Commit ccb16f5c authored by Hrishikesh Pathak's avatar Hrishikesh Pathak

Merge branch 'master' into SEClusterConfigBugFix

parents 88f9d236 a194c28d
...@@ -16,4 +16,6 @@ Mono/ ...@@ -16,4 +16,6 @@ Mono/
redis-cli.exe redis-cli.exe
Redis Configs/*.dat Redis Configs/*.dat
RedisQFork*.dat RedisQFork*.dat
StackExchange.Redis.*.zip StackExchange.Redis.*.zip
\ No newline at end of file .vs/
*.lock.json
...@@ -48,13 +48,13 @@ ...@@ -48,13 +48,13 @@
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj"> <Folder Include="Properties\" />
<Project>{7cec07f2-8c03-4c42-b048-738b215824c1}</Project>
<Name>StackExchange.Redis</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Properties\" /> <ProjectReference Include="..\StackExchange.Redis_Net45\StackExchange.Redis_Net45.csproj">
<Project>{7cec07f2-8c03-4c42-b048-738b215824c1}</Project>
<Name>StackExchange.Redis_Net45</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
......
...@@ -35,9 +35,19 @@ static void MassiveBulkOpsAsync(int AsyncOpsQty, bool preserveOrder, bool withCo ...@@ -35,9 +35,19 @@ static void MassiveBulkOpsAsync(int AsyncOpsQty, bool preserveOrder, bool withCo
var conn = muxer.GetDatabase(); var conn = muxer.GetDatabase();
muxer.Wait(conn.PingAsync()); muxer.Wait(conn.PingAsync());
#if CORE_CLR
int number = 0;
#endif
Action<Task> nonTrivial = delegate Action<Task> nonTrivial = delegate
{ {
#if !CORE_CLR
Thread.SpinWait(5); Thread.SpinWait(5);
#else
for (int i = 0; i < 50; i++)
{
number++;
}
#endif
}; };
var watch = Stopwatch.StartNew(); var watch = Stopwatch.StartNew();
for (int i = 0; i <= AsyncOpsQty; i++) for (int i = 0; i <= AsyncOpsQty; i++)
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>9d83baba-a92e-495f-bf63-deb4f6b09355</ProjectGuid>
<RootNamespace>BasicTest_dnxcore50</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
\ No newline at end of file
{
"version": "1.0.0-*",
"description": "StackExchange.Redis.BasicTest dnxcore50",
"buildOptions": {
"compile": {
"include": [
"../BasicTest/Program.cs"
]
}
},
"dependencies": {
"StackExchange.Redis": {
"version": "1.1.*",
"target": "project"
}
},
"commands": {
"run": "BasicTest_dnxcore50"
},
"frameworks": {
"netcoreapp1.0": {
"buildOptions": {
"define": [ "CORE_CLR" ]
},
"imports": [ "dnxcore50" ],
"dependencies": {
"System.Console": "4.0.0-rc2-24027"
}
}
}
}
...@@ -113,9 +113,9 @@ ...@@ -113,9 +113,9 @@
<None Include="App.config" /> <None Include="App.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj"> <ProjectReference Include="..\StackExchange.Redis_Net45\StackExchange.Redis_Net45.csproj">
<Project>{7cec07f2-8c03-4c42-b048-738b215824c1}</Project> <Project>{7cec07f2-8c03-4c42-b048-738b215824c1}</Project>
<Name>StackExchange.Redis</Name> <Name>StackExchange.Redis_Net45</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
......
...@@ -3,16 +3,20 @@ ...@@ -3,16 +3,20 @@
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): 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):
using StackExchange.Redis; ```C#
... using StackExchange.Redis;
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost"); ...
// ^^^ store and re-use this!!! ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
// ^^^ store and re-use this!!!
```
Note that `ConnectionMultiplexer` implements `IDisposable` and can be disposed when no longer required, but I am 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. 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 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):
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:6379,server2:6379"); ```C#
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 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.
...@@ -27,49 +31,63 @@ Using a redis database ...@@ -27,49 +31,63 @@ Using a redis database
Accessing a redis database is as simple as: Accessing a redis database is as simple as:
IDatabase db = redis.GetDatabase(); ```C#
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: 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:
int databaseNumber = ... ```C#
object asyncState = ... int databaseNumber = ...
IDatabase db = redis.GetDatabase(databaseNumber, asyncState); 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](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.
The simplest operation would be to store and retrieve a value: The simplest operation would be to store and retrieve a value:
string value = "abcdefg"; ```C#
db.StringSet("mykey", value); string value = "abcdefg";
... db.StringSet("mykey", value);
string value = db.StringGet("mykey"); ...
Console.WriteLine(value); // writes: "abcdefg" 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](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:
byte[] key = ..., value = ...; ```C#
db.StringSet(key, value); byte[] key = ..., value = ...;
... db.StringSet(key, value);
byte[] value = db.StringGet(key); ...
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](http://redis.io/commands) covering all redis data types is available for use.
Using redis pub/sub Using redis pub/sub
---- ----
Another common use of redis is as a [pub/sub message](http://redis.io/topics/pubsub) distribution tool; this too is 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](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.
ISubscriber sub = redis.GetSubscriber(); ```C#
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](http://stackoverflow.com)). As is common in .NET, subscriptions take the form of callback delegates which accept the channel-name and the message:
sub.Subscribe("messages", (channel, message) => { ```C#
Console.WriteLine((string)message); sub.Subscribe("messages", (channel, message) => {
}); Console.WriteLine((string)message);
});
```
Separately (and often in a separate process on a separate machine) you can publish to this channel: Separately (and often in a separate process on a separate machine) you can publish to this channel:
sub.Publish("messages", "hello"); ```C#
sub.Publish("messages", "hello");
```
This will (virtually instantaneously) write `"hello"` to the console of the subscribed process. As before, both channel-names and messages can be binary. This will (virtually instantaneously) write `"hello"` to the console of the subscribed process. As before, both channel-names and messages can be binary.
...@@ -80,16 +98,22 @@ Accessing individual servers ...@@ -80,16 +98,22 @@ Accessing individual servers
For maintenance purposes, it is sometimes necessary to issue server-specific commands: For maintenance purposes, it is sometimes necessary to issue server-specific commands:
IServer server = redis.GetServer("localhost", 6379); ```C#
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`](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:
EndPoint[] endpoints = redis.GetEndPoints(); ```C#
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](http://redis.io/commands#server) are available; for example:
DateTime lastSave = server.LastSave(); ```C#
ClientInfo[] clients = server.ClientList(); DateTime lastSave = server.LastSave();
ClientInfo[] clients = server.ClientList();
```
Sync vs Async vs Fire-and-Forget Sync vs Async vs Fire-and-Forget
--- ---
...@@ -107,15 +131,19 @@ The synchronous usage is already shown in the examples above. This is the simple ...@@ -107,15 +131,19 @@ The synchronous usage is already shown in the examples above. This is the simple
For asynchronous usage, the key difference is the `Async` suffix on methods, and (typically) the use of the `await` language feature. For example: For asynchronous usage, the key difference is the `Async` suffix on methods, and (typically) the use of the `await` language feature. For example:
string value = "abcdefg"; ```C#
await db.StringSetAsync("mykey", value); string value = "abcdefg";
... await db.StringSetAsync("mykey", value);
string value = await db.StringGetAsync("mykey"); ...
Console.WriteLine(value); // writes: "abcdefg" string value = await db.StringGetAsync("mykey");
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: 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:
db.StringIncrement(pageKey, flags: CommandFlags.FireAndForget); ```C#
db.StringIncrement(pageKey, flags: CommandFlags.FireAndForget);
```
......
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
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`): 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`):
var conn = ConnectionMultiplexer.Connect(configuration); ```C#
var conn = ConnectionMultiplexer.Connect(configuration);
```
The `configuration` here can be either: The `configuration` here can be either:
...@@ -17,32 +19,43 @@ Basic Configuration Strings ...@@ -17,32 +19,43 @@ Basic Configuration Strings
The *simplest* configuration example is just the host name: The *simplest* configuration example is just the host name:
var conn = ConnectionMultiplexer.Connect("localhost"); ```C#
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: 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:
var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,allowAdmin=true"); ```C#
var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,allowAdmin=true");
```
An overview of mapping between the `string` and `ConfigurationOptions` representation is shown below, but you can switch between them trivially: An overview of mapping between the `string` and `ConfigurationOptions` representation is shown below, but you can switch between them trivially:
ConfigurationOptions options = ConfigurationOptions.Parse(configString); ```C#
ConfigurationOptions options = ConfigurationOptions.Parse(configString);
```
or: or:
string configString = options.ToString(); ```C#
string configString = options.ToString();
```
A common usage is to store the *basic* details in a string, and then apply specific details at runtime: A common usage is to store the *basic* details in a string, and then apply specific details at runtime:
string configString = GetRedisConfiguration(); ```C#
var options = ConfigurationOptions.Parse(configString); string configString = GetRedisConfiguration();
options.ClientName = GetAppName(); // only known at runtime var options = ConfigurationOptions.Parse(configString);
options.AllowAdmin = true; options.ClientName = GetAppName(); // only known at runtime
conn = ConnectionMultiplexer.Connect(options); options.AllowAdmin = true;
conn = ConnectionMultiplexer.Connect(options);
```
Microsoft Azure Redis example with password Microsoft Azure Redis example with password
var conn = ConnectionMultiplexer.Connect("contoso5.redis.cache.windows.net,ssl=true,password=..."); ```C#
var conn = ConnectionMultiplexer.Connect("contoso5.redis.cache.windows.net,ssl=true,password=...");
```
Configuration Options Configuration Options
--- ---
...@@ -79,56 +92,65 @@ Automatic and Manual Configuration ...@@ -79,56 +92,65 @@ 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 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:
ConfigurationOptions config = new ConfigurationOptions ```C#
ConfigurationOptions config = new ConfigurationOptions
{
EndPoints =
{ {
EndPoints = { "redis0", 6379 },
{ { "redis1", 6380 }
{ "redis0", 6379 }, },
{ "redis1", 6380 } CommandMap = CommandMap.Create(new HashSet<string>
}, { // EXCLUDE a few commands
CommandMap = CommandMap.Create(new HashSet<string> "INFO", "CONFIG", "CLUSTER",
{ // EXCLUDE a few commands "PING", "ECHO", "CLIENT"
"INFO", "CONFIG", "CLUSTER", }, available: false),
"PING", "ECHO", "CLIENT" KeepAlive = 180,
}, available: false), DefaultVersion = new Version(2, 8, 8),
KeepAlive = 180, Password = "changeme"
DefaultVersion = new Version(2, 8, 8), };
Password = "changeme" ```
};
Which is equivalent to the command string: Which is equivalent to the command string:
redis0:6379,redis1:6380,keepAlive=180,version=2.8.8,$CLIENT=,$CLUSTER=,$CONFIG=,$ECHO=,$INFO=,$PING= ```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<string>` to `Create()` (to indicate the available or unavailable commands), you pass a `Dictionary<string,string>`. 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: 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<string>` to `Create()` (to indicate the available or unavailable commands), you pass a `Dictionary<string,string>`. 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:
var commands = new Dictionary<string,string> { ```C#
{ "info", null }, // disabled var commands = new Dictionary<string,string> {
{ "select", "use" }, // renamed to SQL equivalent for some reason { "info", null }, // disabled
}; { "select", "use" }, // renamed to SQL equivalent for some reason
var options = new ConfigurationOptions { };
// ... var options = new ConfigurationOptions {
CommandMap = CommandMap.Create(commands), // ...
// ... CommandMap = CommandMap.Create(commands),
} // ...
}
```
The above is equivalent to (in the connection string): The above is equivalent to (in the connection string):
$INFO=,$SELECT=use ```config
$INFO=,$SELECT=use
```
Twemproxy 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:
var options = new ConfigurationOptions ```C#
{ var options = new ConfigurationOptions
EndPoints = { "my-server" }, {
Proxy = Proxy.Twemproxy EndPoints = { "my-server" },
}; Proxy = Proxy.Twemproxy
};
```
Tiebreakers and Configuration Change Announcements Tiebreakers and Configuration Change Announcements
--- ---
......
...@@ -62,17 +62,18 @@ ...@@ -62,17 +62,18 @@
<None Include="Profiling.md" /> <None Include="Profiling.md" />
<None Include="PubSubOrder.md" /> <None Include="PubSubOrder.md" />
<None Include="PipelinesMultiplexers.md" /> <None Include="PipelinesMultiplexers.md" />
<None Include="Timeouts.md" />
<None Include="Transactions.md" /> <None Include="Transactions.md" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="KeysValues.md" /> <None Include="KeysValues.md" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>
\ No newline at end of file
...@@ -38,16 +38,18 @@ So how do I use them? ...@@ -38,16 +38,18 @@ So how do I use them?
Simple: start from a server, not a database. Simple: start from a server, not a database.
// get the target server ```C#
var server = conn.GetServer(someServer); // get the target server
var server = conn.GetServer(someServer);
// show all keys in database 0 that include "foo" in their name
foreach(var key in server.Keys(pattern: "*foo*")) { // show all keys in database 0 that include "foo" in their name
Console.WriteLine(key); foreach(var key in server.Keys(pattern: "*foo*")) {
} Console.WriteLine(key);
}
// completely wipe ALL keys from database 0
server.FlushDatabase(); // completely wipe ALL keys from database 0
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`. 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`.
......
...@@ -18,48 +18,64 @@ Keys ...@@ -18,48 +18,64 @@ 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: 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:
string key = ... ```C#
db.StringIncrement(key); string key = ...
db.StringIncrement(key);
```
or or
byte[] key = ... ```C#
db.StringIncrement(key); byte[] key = ...
db.StringIncrement(key);
```
Likewise, there are operations that *return* keys as `RedisKey` - and again, it simply works: Likewise, there are operations that *return* keys as `RedisKey` - and again, it simply works:
string someKey = db.KeyRandom(); ```C#
string someKey = db.KeyRandom();
```
Values 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: 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:
db.StringSet("mykey", "myvalue"); ```C#
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`: 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`:
db.StringSet("mykey", 123); // this is still a RedisKey and RedisValue ```C#
... db.StringSet("mykey", 123); // this is still a RedisKey and RedisValue
int i = (int)db.StringGet("mykey"); ...
int i = (int)db.StringGet("mykey");
```
Note that while the conversions from primitives to `RedisValue` are implicit, many of the conversions from `RedisValue` to primitives are explicit: this is because it is very possible that these conversions will fail if the data does not have an appropriate value. Note that while the conversions from primitives to `RedisValue` are implicit, many of the conversions from `RedisValue` to primitives are explicit: this is because it is very possible that these conversions will fail if the data does not have an appropriate value.
Note additionally that *when treated numerically*, redis treats a non-existent key as zero; for consistency with this, nil responses are treated as zero: Note additionally that *when treated numerically*, redis treats a non-existent key as zero; for consistency with this, nil responses are treated as zero:
db.KeyDelete("abc"); ```C#
int i = (int)db.StringGet("abc"); // this is ZERO 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: If you need to detect the nil condition, then you can check for that:
db.KeyDelete("abc"); ```C#
var value = db.StringGet("abc"); db.KeyDelete("abc");
bool isNil = value.IsNull; // this is true var value = db.StringGet("abc");
bool isNil = value.IsNull; // this is true
```
or perhaps more simply, just use the provided `Nullable<T>` support: or perhaps more simply, just use the provided `Nullable<T>` support:
db.KeyDelete("abc"); ```C#
var value = (int?)db.StringGet("abc"); // behaves as you would expect db.KeyDelete("abc");
var value = (int?)db.StringGet("abc"); // behaves as you would expect
```
Hashes Hashes
--- ---
...@@ -81,14 +97,18 @@ Scripting ...@@ -81,14 +97,18 @@ Scripting
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: 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:
var result = db.ScriptEvaluate(TransferScript, ```C#
new RedisKey[] { from, to }, new RedisValue[] { quantity }); var result = db.ScriptEvaluate(TransferScript,
new RedisKey[] { from, to }, new RedisValue[] { quantity });
```
(where `TransferScript` is some `string` containing Lua, not shown for this example) (where `TransferScript` is some `string` containing Lua, not shown for this example)
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: 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:
string[] items = db.ScriptEvaluate(...); ```C#
string[] items = db.ScriptEvaluate(...);
```
Conclusion Conclusion
--- ---
......
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
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* (one of the several reasons that continuation-based programming is becoming increasingly popular. Let's consider some regular procedural code: 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* (one of the several reasons that continuation-based programming is becoming increasingly popular. Let's consider some regular procedural code:
string a = db.StringGet("a"); ```C#
string b = db.StringGet("b"); string a = db.StringGet("a");
string b = db.StringGet("b");
```
In terms of steps involved, this looks like: In terms of steps involved, this looks like:
...@@ -40,10 +42,12 @@ Because of this, many redis clients allow you to make use of *pipelining*; this ...@@ -40,10 +42,12 @@ 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: For example, to pipeline the two gets using procedural (blocking) code, we could use:
var aPending = db.StringGetAsync("a"); ```C#
var bPending = db.StringGetAsync("b"); var aPending = db.StringGetAsync("a");
var a = db.Wait(aPending); var bPending = db.StringGetAsync("b");
var b = db.Wait(bPending); var a = db.Wait(aPending);
var b = db.Wait(bPending);
```
Note that I'm using `db.Wait` here because it will automatically apply the configured synchronous timeout, but you can use `aPending.Wait()` or `Task.WaitAll(aPending, bPending);` if you prefer. Using pipelining allows us to get both requests onto the network immediately, eliminating most of the latency. Additionally, it also helps reduce packet fragmentation: 20 requests sent individually (waiting for each response) will require at least 20 packets, but 20 requests sent in a pipeline could fit into much fewer packets (perhaps even just one). Note that I'm using `db.Wait` here because it will automatically apply the configured synchronous timeout, but you can use `aPending.Wait()` or `Task.WaitAll(aPending, bPending);` if you prefer. Using pipelining allows us to get both requests onto the network immediately, eliminating most of the latency. Additionally, it also helps reduce packet fragmentation: 20 requests sent individually (waiting for each response) will require at least 20 packets, but 20 requests sent in a pipeline could fit into much fewer packets (perhaps even just one).
...@@ -52,26 +56,30 @@ Fire and Forget ...@@ -52,26 +56,30 @@ 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: 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:
// sliding expiration ```C#
db.KeyExpire(key, TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget); // sliding expiration
var value = (string)db.StringGet(key); db.KeyExpire(key, TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget);
var value = (string)db.StringGet(key);
```
The `FireAndForget` flag causes the client library to queue the work as normal, but immediately return a default value (since `KeyExpire` returns a `bool`, this will return `false`, because `default(bool)` is `false` - however the return value is meaningless and should be ignored). This works for `*Async` methods too: an already-completed `Task<T>` is returned with the default value (or an already-completed `Task` is returned for `void` methods). The `FireAndForget` flag causes the client library to queue the work as normal, but immediately return a default value (since `KeyExpire` returns a `bool`, this will return `false`, because `default(bool)` is `false` - however the return value is meaningless and should be ignored). This works for `*Async` methods too: an already-completed `Task<T>` is returned with the default value (or an already-completed `Task` is returned for `void` methods).
Multiplexing Multiplexing
--- ---
Pipelining is all well and good, but often any single block of code only want 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. 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](https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Transactions.md). 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](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](https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Transactions.md). If you feel you want "blocking pops", then I strongly suggest you consider pub/sub instead:
sub.Subscribe(channel, delegate { ```C#
string work = db.ListRightPop(key); sub.Subscribe(channel, delegate {
if (work != null) Process(work); string work = db.ListRightPop(key);
}); if (work != null) Process(work);
//... });
db.ListLeftPush(key, newWork, flags: CommandFlags.FireAndForget); //...
sub.Publish(channel, ""); db.ListLeftPush(key, newWork, flags: CommandFlags.FireAndForget);
sub.Publish(channel, "");
```
This achieves the same intent without requiring blocking operations. Notes: This achieves the same intent without requiring blocking operations. Notes:
...@@ -88,13 +96,15 @@ Concurrency ...@@ -88,13 +96,15 @@ 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: 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:
string value = await db.StringGet(key); ```C#
if (value == null) { string value = await db.StringGet(key);
value = await ComputeValueFromDatabase(...); if (value == null) {
db.StringSet(key, value, flags: CommandFlags.FireAndForget); value = await ComputeValueFromDatabase(...);
} db.StringSet(key, value, flags: CommandFlags.FireAndForget);
return value; }
return value;
```
[1]: http://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx [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 [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 [3]: http://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx
\ No newline at end of file
...@@ -45,7 +45,7 @@ command is sent (via the `IProfiler` interface's `GetContext()` method). ...@@ -45,7 +45,7 @@ command is sent (via the `IProfiler` interface's `GetContext()` method).
A toy example of associating commands issued from many different threads together A toy example of associating commands issued from many different threads together
``` ```C#
class ToyProfiler : IProfiler class ToyProfiler : IProfiler
{ {
public ConcurrentDictionary<Thread, object> Contexts = new ConcurrentDictionary<Thread, object>(); public ConcurrentDictionary<Thread, object> Contexts = new ConcurrentDictionary<Thread, object>();
...@@ -59,7 +59,6 @@ class ToyProfiler : IProfiler ...@@ -59,7 +59,6 @@ class ToyProfiler : IProfiler
} }
} }
// ... // ...
ConnectionMultiplexer conn = /* initialization */; ConnectionMultiplexer conn = /* initialization */;
...@@ -107,7 +106,7 @@ At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for e ...@@ -107,7 +106,7 @@ At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for e
If instead you did the following: If instead you did the following:
``` ```C#
ConnectionMultiplexer conn = /* initialization */; ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler(); var profiler = new ToyProfiler();
...@@ -156,7 +155,7 @@ Moving away from toy examples, here's how you can profile StackExchange.Redis in ...@@ -156,7 +155,7 @@ Moving away from toy examples, here's how you can profile StackExchange.Redis in
First register the following `IProfiler` against your `ConnectionMultiplexer`: First register the following `IProfiler` against your `ConnectionMultiplexer`:
``` ```C#
public class RedisProfiler : IProfiler public class RedisProfiler : IProfiler
{ {
const string RequestContextKey = "RequestProfilingContext"; const string RequestContextKey = "RequestProfilingContext";
...@@ -184,7 +183,7 @@ public class RedisProfiler : IProfiler ...@@ -184,7 +183,7 @@ public class RedisProfiler : IProfiler
Then, add the following to your Global.asax.cs file: Then, add the following to your Global.asax.cs file:
``` ```C#
protected void Application_BeginRequest() protected void Application_BeginRequest()
{ {
var ctxObj = RedisProfiler.CreateContextForCurrentRequest(); var ctxObj = RedisProfiler.CreateContextForCurrentRequest();
......
...@@ -12,7 +12,9 @@ This works *particularly* well if messages are generally unrelated. ...@@ -12,7 +12,9 @@ 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: For safety, **the default is sequential**; however, it is strongly recommended that you use concurrent processing whenever possible. This is a simple change:
multiplexer.PreserveAsyncOrder = false; ```C#
multiplexer.PreserveAsyncOrder = false;
```
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 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. messages.
\ No newline at end of file
...@@ -24,7 +24,7 @@ An example use of the `LuaScript`: ...@@ -24,7 +24,7 @@ An example use of the `LuaScript`:
The `LuaScript` class rewrites variables in scripts of the form `@myVar` into the appropriate `ARGV[someIndex]` required by redis. If the The `LuaScript` class rewrites variables in scripts of the form `@myVar` into the appropriate `ARGV[someIndex]` required by redis. If the
parameter passed is of type `RedisKey` it will be sent as part of the `KEYS` collection automatically. parameter passed is of type `RedisKey` it will be sent as part of the `KEYS` collection automatically.
Any object that exposes field or property members with the same name as @-prefixed variables in the Lua script can be used as a paramter hash to Any object that exposes field or property members with the same name as @-prefixed variables in the Lua script can be used as a parameter hash to
`Evaluate` calls. Supported member types are the following: `Evaluate` calls. Supported member types are the following:
- int(?) - int(?)
......
Are you getting network or CPU bound?
---------------
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?
---------------
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] (http://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?
---------------
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.
- 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).
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.
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
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.
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).
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.
Note that StackExchange.Redis can hit timeouts if growth of either IOCP or WORKER threads gets throttled.
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.
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 `<processModel>` configuration element in machine.config. If you are running inside of Azure WebSites, this setting is not exposed through the configuration options. You should be able to set this programmatically (see below) from your Application_Start method in global.asax.cs.
> **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 `<processModel minIoThreads="50"/>`.
- Outside of ASP.NET, use the [ThreadPool.SetMinThreads(…)](https://msdn.microsoft.com//en-us/library/system.threading.threadpool.setminthreads(v=vs.100).aspx) API.
...@@ -11,20 +11,22 @@ all applied in a single unit (i.e. without other connections getting time betwee ...@@ -11,20 +11,22 @@ 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* 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): the transaction. For example, in a SQL database you might do the following (pseudo-code - illustrative only):
// assign a new unique id only if they don't already ```C#
// have one, in a transaction to ensure no thread-races // assign a new unique id only if they don't already
var newId = CreateNewUniqueID(); // optimistic // have one, in a transaction to ensure no thread-races
using(var tran = conn.BeginTran()) var newId = CreateNewUniqueID(); // optimistic
using(var tran = conn.BeginTran())
{
var cust = GetCustomer(conn, custId, tran);
var uniqueId = cust.UniqueID;
if(uniqueId == null)
{ {
var cust = GetCustomer(conn, custId, tran); cust.UniqueId = newId;
var uniqueId = cust.UniqueID; SaveCustomer(conn, cust, tran);
if(uniqueId == null)
{
cust.UniqueId = newId;
SaveCustomer(conn, cust, tran);
}
tran.Complete();
} }
tran.Complete();
}
```
So how do you do it in Redis? So how do you do it in Redis?
--- ---
...@@ -39,14 +41,16 @@ you *can* do is: `WATCH` a key, check data from that key in the normal way, then ...@@ -39,14 +41,16 @@ 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 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: forget all the watched keys. Note that watched keys are also reset during `EXEC` and `DISCARD`. So *at the Redis layer*, this is conceptually:
WATCH {custKey} ```
HEXISTS {custKey} "UniqueId" WATCH {custKey}
(check the reply, then either:) HEXISTS {custKey} "UniqueId"
MULTI (check the reply, then either:)
HSET {custKey} "UniqueId" {newId} MULTI
EXEC HSET {custKey} "UniqueId" {newId}
(or, if we find there was already an unique-id:) EXEC
UNWATCH (or, if we find there was already an unique-id:)
UNWATCH
```
This might look odd - having a `MULTI`/`EXEC` that only spans a single operation - but the important thing This might look odd - having a `MULTI`/`EXEC` that only spans a single operation - but the important thing
is that we are now also tracking changes to `{custKey}` from all other connections - if anyone else is that we are now also tracking changes to `{custKey}` from all other connections - if anyone else
...@@ -62,12 +66,14 @@ basically pre-canned tests involving `WATCH`, some kind of test, and a check on ...@@ -62,12 +66,14 @@ 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 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: mixed together with other callers. So our example becomes:
var newId = CreateNewId(); ```C#
var tran = db.CreateTransaction(); var newId = CreateNewId();
tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID")); var tran = db.CreateTransaction();
tran.HashSetAsync(custKey, "UniqueID", newId); tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID"));
bool committed = tran.Execute(); tran.HashSetAsync(custKey, "UniqueID", newId);
// ^^^ if true: it was applied; if false: it was rolled back bool committed = tran.Execute();
// ^^^ if true: it was applied; if false: it was rolled back
```
Note that the object returned from `CreateTransaction` only has access to the *async* methods - because the result of Note that the object returned from `CreateTransaction` only has access to the *async* methods - because the result of
each operation will not be known until after `Execute` (or `ExecuteAsync`) has completed. If the operations are not applied, all the `Task`s each operation will not be known until after `Execute` (or `ExecuteAsync`) has completed. If the operations are not applied, all the `Task`s
...@@ -82,8 +88,10 @@ Inbuilt operations via `When` ...@@ -82,8 +88,10 @@ 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 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: atomic commands exist. These are accessed via the `When` parameter - so our previous example can *also* be written as:
var newId = CreateNewId(); ```C#
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists); var newId = CreateNewId();
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);
```
(here, the `When.NotExists` causes the `HSETNX` command to be used, rather than `HSET`) (here, the `When.NotExists` causes the `HSETNX` command to be used, rather than `HSET`)
...@@ -96,11 +104,15 @@ between the caller and the server, but the trade-off is that it monopolises the ...@@ -96,11 +104,15 @@ between the caller and the server, but the trade-off is that it monopolises the
At the Redis layer (and assuming `HSETNX` did not exist) this could be implemented as: At the Redis layer (and assuming `HSETNX` did not exist) this could be implemented as:
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} ```
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: This can be used in StackExchange.Redis via:
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", ```C#
new RedisKey[] { custKey }, new RedisValue[] { newId }); 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 });
```
(note that the response from `ScriptEvaluate` and `ScriptEvaluateAsync` is variable depending on your exact script; the response can be interpreted by casting - in this case as a `bool`) (note that the response from `ScriptEvaluate` and `ScriptEvaluateAsync` is variable depending on your exact script; the response can be interpreted by casting - in this case as a `bool`)
...@@ -122,25 +122,36 @@ public void CanOpenSecuredConnection() ...@@ -122,25 +122,36 @@ public void CanOpenSecuredConnection()
} }
} }
[Test, ExpectedException(typeof(RedisConnectionException))] [Test]
public void CanNotOpenNonsenseConnection_IP() public void CanNotOpenNonsenseConnection_IP()
{ {
var log = new StringWriter(); Assert.Throws<RedisConnectionException>(() =>
try { {
using (var conn = ConnectionMultiplexer.Connect(Config.LocalHost + ":6500")) { } var log = new StringWriter();
} finally { try {
Console.WriteLine(log); using (var conn = ConnectionMultiplexer.Connect(Config.LocalHost + ":6500")) { }
} }
finally {
Console.WriteLine(log);
}
});
} }
[Test, ExpectedException(typeof(RedisConnectionException))]
[Test]
public void CanNotOpenNonsenseConnection_DNS() public void CanNotOpenNonsenseConnection_DNS()
{ {
var log = new StringWriter(); Assert.Throws<RedisConnectionException>(() =>
try { {
using (var conn = ConnectionMultiplexer.Connect("doesnot.exist.ds.aasd981230d.com:6500", log)) { } var log = new StringWriter();
} finally { try
Console.WriteLine(log); {
} using (var conn = ConnectionMultiplexer.Connect("doesnot.exist.ds.aasd981230d.com:6500", log)) { }
}
finally
{
Console.WriteLine(log);
}
});
} }
[Test] [Test]
...@@ -176,6 +187,63 @@ public void CreateDisconnectedNonsenseConnection_DNS() ...@@ -176,6 +187,63 @@ public void CreateDisconnectedNonsenseConnection_DNS()
} }
} }
[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) internal static void AssertNearlyEqual(double x, double y)
{ {
if (Math.Abs(x - y) > 0.00001) Assert.AreEqual(x, y); if (Math.Abs(x - y) > 0.00001) Assert.AreEqual(x, y);
......
...@@ -35,7 +35,7 @@ public void TestManualIncr() ...@@ -35,7 +35,7 @@ public void TestManualIncr()
public async Task<long?> ManualIncr(IDatabase connection, string key) public async Task<long?> ManualIncr(IDatabase connection, string key)
{ {
var oldVal = (long?)await connection.StringGetAsync(key); var oldVal = (long?)await connection.StringGetAsync(key).ConfigureAwait(false);
var newVal = (oldVal ?? 0) + 1; var newVal = (oldVal ?? 0) + 1;
var tran = connection.CreateTransaction(); var tran = connection.CreateTransaction();
{ // check hasn't changed { // check hasn't changed
...@@ -44,7 +44,7 @@ public void TestManualIncr() ...@@ -44,7 +44,7 @@ public void TestManualIncr()
tran.AddCondition(Condition.StringEqual(key, oldVal)); tran.AddCondition(Condition.StringEqual(key, oldVal));
tran.StringSetAsync(key, newVal); tran.StringSetAsync(key, newVal);
#pragma warning restore 4014 #pragma warning restore 4014
if (!await tran.ExecuteAsync()) return null; // aborted if (!await tran.ExecuteAsync().ConfigureAwait(false)) return null; // aborted
return newVal; return newVal;
} }
} }
......
...@@ -55,28 +55,33 @@ public void ExecuteWithEmptyStartingPoint() ...@@ -55,28 +55,33 @@ public void ExecuteWithEmptyStartingPoint()
} }
} }
[Test, ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")] [Test]
public void ExecuteWithNonHashStartingPoint() public void ExecuteWithNonHashStartingPoint()
{ {
using (var muxer = Config.GetUnsecuredConnection()) Assert.Throws<RedisConnectionException>(() =>
{ {
var conn = muxer.GetDatabase(0); using (var muxer = Config.GetUnsecuredConnection())
var task = new { priority = 3 }; {
conn.KeyDeleteAsync("item:1"); var conn = muxer.GetDatabase(0);
conn.StringSetAsync("item:1", "not a hash"); var task = new { priority = 3 };
conn.HashSetAsync("item:1", "priority", task.priority.ToString()); 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"); var taskResult = conn.HashGetAsync("item:1", "priority");
try try
{ {
conn.Wait(taskResult); conn.Wait(taskResult);
Assert.Fail(); Assert.Fail();
} catch(AggregateException ex) }
{ catch (AggregateException ex)
throw ex.InnerExceptions[0]; {
throw ex.InnerExceptions[0];
}
} }
} },
message: "WRONGTYPE Operation against a key holding the wrong kind of value");
} }
} }
} }
...@@ -44,9 +44,9 @@ ...@@ -44,9 +44,9 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.0.5797.27534, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <HintPath>..\packages\NUnit.3.0.0\lib\net45\nunit.framework.dll</HintPath>
<HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
...@@ -87,13 +87,13 @@ ...@@ -87,13 +87,13 @@
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj"> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
<Project>{7cec07f2-8c03-4c42-b048-738b215824c1}</Project>
<Name>StackExchange.Redis</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> <ProjectReference Include="..\StackExchange.Redis_Net45\StackExchange.Redis_Net45.csproj">
<Project>{7cec07f2-8c03-4c42-b048-738b215824c1}</Project>
<Name>StackExchange.Redis_Net45</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
......
...@@ -100,7 +100,6 @@ where Attribute.IsDefined(method, typeof(TestAttribute)) ...@@ -100,7 +100,6 @@ where Attribute.IsDefined(method, typeof(TestAttribute))
foreach (var test in tests) foreach (var test in tests)
{ {
var expectedFail = Attribute.GetCustomAttribute(test, typeof(ExpectedExceptionAttribute)) as ExpectedExceptionAttribute;
Console.Write(test.Name + ": "); Console.Write(test.Name + ": ");
Exception err = null; Exception err = null;
...@@ -131,35 +130,6 @@ where Attribute.IsDefined(method, typeof(TestAttribute)) ...@@ -131,35 +130,6 @@ where Attribute.IsDefined(method, typeof(TestAttribute))
err = ((AggregateException)err).InnerExceptions[0]; err = ((AggregateException)err).InnerExceptions[0];
} }
if (expectedFail != null)
{
if (err == null)
{
err = new NUnit.Framework.AssertionException("failed to fail");
}
else
{
int issues = 0;
if (expectedFail.ExpectedException != null && !expectedFail.ExpectedException.IsAssignableFrom(err.GetType()))
{
issues++;
}
if (expectedFail.ExpectedExceptionName != null && err.GetType().FullName != expectedFail.ExpectedExceptionName)
{
issues++;
}
if (expectedFail.ExpectedMessage != null && err.Message != expectedFail.ExpectedMessage)
{
issues++;
}
if (issues == 0) err = null;
else
{
err = new InvalidOperationException("Failed in a different way", err);
}
}
}
if (err == null) if (err == null)
{ {
Console.WriteLine("pass"); Console.WriteLine("pass");
...@@ -200,8 +170,7 @@ where Attribute.IsDefined(method, typeof(TestAttribute)) ...@@ -200,8 +170,7 @@ where Attribute.IsDefined(method, typeof(TestAttribute))
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class ActiveTestAttribute : Attribute public sealed class ActiveTestAttribute : Attribute
{ {
private readonly int count; public int Count { get; }
public int Count { get { return count; } }
public ActiveTestAttribute() : this(1) { } public ActiveTestAttribute() : this(1) { }
public ActiveTestAttribute(int count) { this.count = count; } public ActiveTestAttribute(int count) { this.Count = count; }
} }
\ No newline at end of file
...@@ -264,21 +264,26 @@ public void NonAsciiScripts() ...@@ -264,21 +264,26 @@ public void NonAsciiScripts()
} }
} }
[Test, ExpectedException(typeof(RedisServerException), ExpectedMessage = "oops")] [Test]
public void ScriptThrowsError() public void ScriptThrowsError()
{ {
using (var muxer = GetScriptConn()) Assert.Throws<RedisServerException>(() =>
{ {
var conn = muxer.GetDatabase(0); using (var muxer = GetScriptConn())
var result = conn.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null);
try
{ {
conn.Wait(result); var conn = muxer.GetDatabase(0);
} catch(AggregateException ex) var result = conn.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null);
{ try
throw ex.InnerExceptions[0]; {
conn.Wait(result);
}
catch (AggregateException ex)
{
throw ex.InnerExceptions[0];
}
} }
} },
message: "oops");
} }
[Test] [Test]
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NUnit" version="2.6.4" targetFramework="net45" /> <package id="NUnit" version="3.0.0" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
...@@ -46,7 +46,7 @@ public ResponseException(string code) ...@@ -46,7 +46,7 @@ public ResponseException(string code)
public Redis(string host, int port) public Redis(string host, int port)
{ {
if (host == null) if (host == null)
throw new ArgumentNullException("host"); throw new ArgumentNullException(nameof(host));
Host = host; Host = host;
Port = port; Port = port;
...@@ -94,9 +94,9 @@ public int Db ...@@ -94,9 +94,9 @@ public int Db
public void Set(string key, string value) public void Set(string key, string value)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
if (value == null) if (value == null)
throw new ArgumentNullException("value"); throw new ArgumentNullException(nameof(value));
Set(key, Encoding.UTF8.GetBytes(value)); Set(key, Encoding.UTF8.GetBytes(value));
} }
...@@ -104,12 +104,12 @@ public void Set(string key, string value) ...@@ -104,12 +104,12 @@ public void Set(string key, string value)
public void Set(string key, byte[] value) public void Set(string key, byte[] value)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
if (value == null) if (value == null)
throw new ArgumentNullException("value"); throw new ArgumentNullException(nameof(value));
if (value.Length > 1073741824) if (value.Length > 1073741824)
throw new ArgumentException("value exceeds 1G", "value"); throw new ArgumentException("value exceeds 1G", nameof(value));
if (!SendDataCommand(value, "SET {0} {1}\r\n", key, value.Length)) if (!SendDataCommand(value, "SET {0} {1}\r\n", key, value.Length))
throw new Exception("Unable to connect"); throw new Exception("Unable to connect");
...@@ -119,9 +119,9 @@ public void Set(string key, byte[] value) ...@@ -119,9 +119,9 @@ public void Set(string key, byte[] value)
public bool SetNX(string key, string value) public bool SetNX(string key, string value)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
if (value == null) if (value == null)
throw new ArgumentNullException("value"); throw new ArgumentNullException(nameof(value));
return SetNX(key, Encoding.UTF8.GetBytes(value)); return SetNX(key, Encoding.UTF8.GetBytes(value));
} }
...@@ -129,12 +129,12 @@ public bool SetNX(string key, string value) ...@@ -129,12 +129,12 @@ public bool SetNX(string key, string value)
public bool SetNX(string key, byte[] value) public bool SetNX(string key, byte[] value)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
if (value == null) if (value == null)
throw new ArgumentNullException("value"); throw new ArgumentNullException(nameof(value));
if (value.Length > 1073741824) if (value.Length > 1073741824)
throw new ArgumentException("value exceeds 1G", "value"); throw new ArgumentException("value exceeds 1G", nameof(value));
return SendDataExpectInt(value, "SETNX {0} {1}\r\n", key, value.Length) > 0 ? true : false; return SendDataExpectInt(value, "SETNX {0} {1}\r\n", key, value.Length) > 0 ? true : false;
} }
...@@ -147,7 +147,7 @@ public void Set(IDictionary<string, string> dict) ...@@ -147,7 +147,7 @@ public void Set(IDictionary<string, string> dict)
public void Set(IDictionary<string, byte[]> dict) public void Set(IDictionary<string, byte[]> dict)
{ {
if (dict == null) if (dict == null)
throw new ArgumentNullException("dict"); throw new ArgumentNullException(nameof(dict));
var nl = Encoding.UTF8.GetBytes("\r\n"); var nl = Encoding.UTF8.GetBytes("\r\n");
...@@ -173,14 +173,14 @@ public void Set(IDictionary<string, byte[]> dict) ...@@ -173,14 +173,14 @@ public void Set(IDictionary<string, byte[]> dict)
public byte[] Get(string key) public byte[] Get(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectData(null, "GET " + key + "\r\n"); return SendExpectData(null, "GET " + key + "\r\n");
} }
public string GetString(string key) public string GetString(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return Encoding.UTF8.GetString(Get(key)); return Encoding.UTF8.GetString(Get(key));
} }
...@@ -192,12 +192,12 @@ public byte[][] Sort(SortOptions options) ...@@ -192,12 +192,12 @@ public byte[][] Sort(SortOptions options)
public byte[] GetSet(string key, byte[] value) public byte[] GetSet(string key, byte[] value)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
if (value == null) if (value == null)
throw new ArgumentNullException("value"); throw new ArgumentNullException(nameof(value));
if (value.Length > 1073741824) if (value.Length > 1073741824)
throw new ArgumentException("value exceeds 1G", "value"); throw new ArgumentException("value exceeds 1G", nameof(value));
if (!SendDataCommand(value, "GETSET {0} {1}\r\n", key, value.Length)) if (!SendDataCommand(value, "GETSET {0} {1}\r\n", key, value.Length))
throw new Exception("Unable to connect"); throw new Exception("Unable to connect");
...@@ -208,9 +208,9 @@ public byte[] GetSet(string key, byte[] value) ...@@ -208,9 +208,9 @@ public byte[] GetSet(string key, byte[] value)
public string GetSet(string key, string value) public string GetSet(string key, string value)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
if (value == null) if (value == null)
throw new ArgumentNullException("value"); throw new ArgumentNullException(nameof(value));
return Encoding.UTF8.GetString(GetSet(key, Encoding.UTF8.GetBytes(value))); return Encoding.UTF8.GetString(GetSet(key, Encoding.UTF8.GetBytes(value)));
} }
...@@ -466,56 +466,56 @@ byte[] ReadData() ...@@ -466,56 +466,56 @@ byte[] ReadData()
public bool ContainsKey(string key) public bool ContainsKey(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("EXISTS " + key + "\r\n") == 1; return SendExpectInt("EXISTS " + key + "\r\n") == 1;
} }
public bool Remove(string key) public bool Remove(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("DEL " + key + "\r\n", key) == 1; return SendExpectInt("DEL " + key + "\r\n", key) == 1;
} }
public int Remove(params string[] args) public int Remove(params string[] args)
{ {
if (args == null) if (args == null)
throw new ArgumentNullException("args"); throw new ArgumentNullException(nameof(args));
return SendExpectInt("DEL " + string.Join(" ", args) + "\r\n"); return SendExpectInt("DEL " + string.Join(" ", args) + "\r\n");
} }
public int Increment(string key) public int Increment(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("INCR " + key + "\r\n"); return SendExpectInt("INCR " + key + "\r\n");
} }
public int Increment(string key, int count) public int Increment(string key, int count)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("INCRBY {0} {1}\r\n", key, count); return SendExpectInt("INCRBY {0} {1}\r\n", key, count);
} }
public int Decrement(string key) public int Decrement(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("DECR " + key + "\r\n"); return SendExpectInt("DECR " + key + "\r\n");
} }
public int Decrement(string key, int count) public int Decrement(string key, int count)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("DECRBY {0} {1}\r\n", key, count); return SendExpectInt("DECRBY {0} {1}\r\n", key, count);
} }
public KeyType TypeOf(string key) public KeyType TypeOf(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
switch (SendExpectString("TYPE {0}\r\n", key)) switch (SendExpectString("TYPE {0}\r\n", key))
{ {
case "none": case "none":
...@@ -538,30 +538,30 @@ public string RandomKey() ...@@ -538,30 +538,30 @@ public string RandomKey()
public bool Rename(string oldKeyname, string newKeyname) public bool Rename(string oldKeyname, string newKeyname)
{ {
if (oldKeyname == null) if (oldKeyname == null)
throw new ArgumentNullException("oldKeyname"); throw new ArgumentNullException(nameof(oldKeyname));
if (newKeyname == null) if (newKeyname == null)
throw new ArgumentNullException("newKeyname"); throw new ArgumentNullException(nameof(newKeyname));
return SendGetString("RENAME {0} {1}\r\n", oldKeyname, newKeyname)[0] == '+'; return SendGetString("RENAME {0} {1}\r\n", oldKeyname, newKeyname)[0] == '+';
} }
public bool Expire(string key, int seconds) public bool Expire(string key, int seconds)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("EXPIRE {0} {1}\r\n", key, seconds) == 1; return SendExpectInt("EXPIRE {0} {1}\r\n", key, seconds) == 1;
} }
public bool ExpireAt(string key, int time) public bool ExpireAt(string key, int time)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("EXPIREAT {0} {1}\r\n", key, time) == 1; return SendExpectInt("EXPIREAT {0} {1}\r\n", key, time) == 1;
} }
public int TimeToLive(string key) public int TimeToLive(string key)
{ {
if (key == null) if (key == null)
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
return SendExpectInt("TTL {0}\r\n", key); return SendExpectInt("TTL {0}\r\n", key);
} }
...@@ -778,13 +778,13 @@ public byte[][] GetUnionOfSets(params string[] keys) ...@@ -778,13 +778,13 @@ public byte[][] GetUnionOfSets(params string[] keys)
void StoreSetCommands(string cmd, string destKey, params string[] keys) void StoreSetCommands(string cmd, string destKey, params string[] keys)
{ {
if (String.IsNullOrEmpty(cmd)) if (String.IsNullOrEmpty(cmd))
throw new ArgumentNullException("cmd"); throw new ArgumentNullException(nameof(cmd));
if (String.IsNullOrEmpty(destKey)) if (String.IsNullOrEmpty(destKey))
throw new ArgumentNullException("destKey"); throw new ArgumentNullException(nameof(destKey));
if (keys == null) if (keys == null)
throw new ArgumentNullException("keys"); throw new ArgumentNullException(nameof(keys));
SendExpectSuccess("{0} {1} {2}\r\n", cmd, destKey, String.Join(" ", keys)); SendExpectSuccess("{0} {1} {2}\r\n", cmd, destKey, String.Join(" ", keys));
} }
......
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear/>
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
...@@ -19,11 +19,15 @@ Installation ...@@ -19,11 +19,15 @@ Installation
StackExchange.Redis can be installed via the nuget UI (as [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis/)), or via the nuget package manager console: StackExchange.Redis can be installed via the nuget UI (as [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis/)), or via the nuget package manager console:
PM> Install-Package StackExchange.Redis ```PowerShell
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: If you require a strong-named package (because your project is strong-named), then you may wish to use instead:
PM> Install-Package StackExchange.Redis.StrongName ```PowerShell
PM> Install-Package StackExchange.Redis.StrongName
```
([for further reading, see here](http://blog.marcgravell.com/2014/06/snk-we-need-to-talk.html)) ([for further reading, see here](http://blog.marcgravell.com/2014/06/snk-we-need-to-talk.html))
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>StackExchange.Redis.StrongName</id> <id>StackExchange.Redis.StrongName</id>
<version>1.0.312</version> <version>1.1.0</version>
<authors>Stack Exchange inc., marc.gravell</authors> <authors>Stack Exchange inc., marc.gravell</authors>
<owners>Stack Exchange inc., marc.gravell</owners> <owners>Stack Exchange inc., marc.gravell</owners>
<summary>Redis client library</summary> <summary>Redis client library</summary>
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<dependencies> <dependencies>
<group targetFramework="net40"> <group targetFramework="net40">
<dependency id="Microsoft.Bcl" version="1.1.9"/> <dependency id="Microsoft.Bcl" version="1.1.10"/>
<dependency id="Microsoft.Bcl.Async" version="1.0.168"/> <dependency id="Microsoft.Bcl.Async" version="1.0.168"/>
</group> </group>
<group targetFramework="net45"> <group targetFramework="net45">
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</metadata> </metadata>
<files> <files>
<file src="StackExchange.Redis_Net46\bin.snk\Release\StackExchange.Redis*.*" target="lib\net46" /> <file src="StackExchange.Redis_Net46\bin.snk\Release\StackExchange.Redis*.*" target="lib\net46" />
<file src="StackExchange.Redis\bin.snk\Release\StackExchange.Redis*.*" target="lib\net45" /> <file src="StackExchange.Redis_Net45\bin.snk\Release\StackExchange.Redis*.*" target="lib\net45" />
<file src="StackExchange.Redis_Net40\bin.snk\Release\StackExchange.Redis*.*" target="lib\net40" /> <file src="StackExchange.Redis_Net40\bin.snk\Release\StackExchange.Redis*.*" target="lib\net40" />
</files> </files>
</package> </package>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>46754d2a-ac16-4686-b113-3db08acf4269</ProjectGuid>
<RootNamespace>StackExchange.Redis.StrongName</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
\ No newline at end of file
{
"packOptions": {
"summary": "Redis client library",
"tags": [ "Async", "Redis", "Cache", "PubSub", "Messaging" ],
"owners": [ "marc.gravell" ],
"releaseNotes": "Alpha for .NET Core; if you aren't doing .NET Core, you probably don't want this",
"requireLicenseAcceptance": false,
"projectUrl": "https://github.com/StackExchange/StackExchange.Redis",
"licenseUrl": "https://raw.github.com/StackExchange/StackExchange.Redis/master/LICENSE",
"repository": {
"type": "git",
"url": "https://github.com/StackExchange/StackExchange.Redis"
}
},
"title": "StackExchange.Redis.StrongName",
"version": "1.1.604-*",
"description": "High performance Redis client, incorporating both synchronous and asynchronous usage.",
"authors": [ "Stack Exchange inc., marc.gravell" ],
"copyright": "Stack Exchange inc. 2014-",
"dependencies": {
},
"buildOptions": {
"allowUnsafe": true,
"xmlDoc": true,
"keyFile": "../StackExchange.Redis.snk",
"define": [ "STRONG_NAME" ],
"compile": {
"include": [
"../StackExchange.Redis/**/*.cs"
],
"exclude": [
"../StackExchange.Redis/obj/"
],
"excludeFiles": [
"../StackExchange.Redis/Properties/AssemblyInfo.cs"
]
}
},
"frameworks": {
"net40": {
"dependencies": {
"Microsoft.Bcl": "1.1.10",
"Microsoft.Bcl.Async": "1.0.168"
},
"buildOptions": {
"define": [ "FEATURE_SERIALIZATION" ]
}
},
"net45": {
"frameworkAssemblies": {
"System.IO.Compression": "4.0.0.0"
},
"buildOptions": {
"define": [ "FEATURE_SERIALIZATION" ]
}
},
"net46": {
"frameworkAssemblies": {
"System.IO.Compression": "4.0.0.0"
},
"define": [ "FEATURE_SERIALIZATION", "PLAT_SAFE_CONTINUATIONS" ]
},
"netstandard1.5": {
"buildOptions": {
"define": [ "PLAT_SAFE_CONTINUATIONS", "CORE_CLR" ]
},
"dependencies": {
"NETStandard.Library": "1.5.0-rc2-24027",
"System.Collections.NonGeneric": "4.0.1-rc2-24027",
"System.IO.FileSystem": "4.0.1-rc2-24027",
"System.Net.NameResolution": "4.0.0-rc2-24027",
"System.Net.Security": "4.0.0-rc2-24027",
"System.Net.Sockets": "4.1.0-rc2-24027",
"System.Reflection.Emit": "4.0.1-rc2-24027",
"System.Reflection.Emit.Lightweight": "4.0.1-rc2-24027",
"System.Reflection.TypeExtensions": "4.1.0-rc2-24027",
"System.Security.Cryptography.Algorithms": "4.1.0-rc2-24027",
"System.Security.Cryptography.X509Certificates": "4.1.0-rc2-24027",
"System.Threading.Thread": "4.0.0-rc2-24027",
"System.Threading.ThreadPool": "4.0.10-rc2-24027",
"System.Threading.Timer": "4.0.1-rc2-24027"
}
}
}
}
\ No newline at end of file
using System; using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
#if FEATURE_BOOKSLEEVE
using BookSleeve; using BookSleeve;
#endif
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
...@@ -72,26 +74,28 @@ public void PingMany(bool preserveOrder) ...@@ -72,26 +74,28 @@ public void PingMany(bool preserveOrder)
} }
[Test] [Test]
[ExpectedException(typeof(ArgumentException), ExpectedMessage = @"A null key is not valid in this context")]
public void GetWithNullKey() public void GetWithNullKey()
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
var db = muxer.GetDatabase(); var db = muxer.GetDatabase();
string key = null; string key = null;
db.StringGet(key); Assert.Throws<ArgumentException>(
() => db.StringGet(key),
"A null key is not valid in this context");
} }
} }
[Test] [Test]
[ExpectedException(typeof(ArgumentException), ExpectedMessage = @"A null key is not valid in this context")]
public void SetWithNullKey() public void SetWithNullKey()
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
var db = muxer.GetDatabase(); var db = muxer.GetDatabase();
string key = null, value = "abc"; string key = null, value = "abc";
db.StringSet(key, value); Assert.Throws<ArgumentException>(
() => db.StringSet(key, value),
"A null key is not valid in this context");
} }
} }
...@@ -230,9 +234,19 @@ public void MassiveBulkOpsAsync(bool preserveOrder, bool withContinuation) ...@@ -230,9 +234,19 @@ public void MassiveBulkOpsAsync(bool preserveOrder, bool withContinuation)
var conn = muxer.GetDatabase(); var conn = muxer.GetDatabase();
muxer.Wait(conn.PingAsync()); muxer.Wait(conn.PingAsync());
#if CORE_CLR
int number = 0;
#endif
Action<Task> nonTrivial = delegate Action<Task> nonTrivial = delegate
{ {
#if !CORE_CLR
Thread.SpinWait(5); Thread.SpinWait(5);
#else
for (int i = 0; i < 50; i++)
{
number++;
}
#endif
}; };
var watch = Stopwatch.StartNew(); var watch = Stopwatch.StartNew();
for (int i = 0; i <= AsyncOpsQty; i++) for (int i = 0; i <= AsyncOpsQty; i++)
...@@ -293,7 +307,6 @@ public void GetWithExpiry(bool exists, bool hasExpiry) ...@@ -293,7 +307,6 @@ public void GetWithExpiry(bool exists, bool hasExpiry)
} }
} }
[Test] [Test]
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")]
public void GetWithExpiryWrongTypeAsync() public void GetWithExpiryWrongTypeAsync()
{ {
using (var conn = Create()) using (var conn = Create())
...@@ -302,33 +315,41 @@ public void GetWithExpiryWrongTypeAsync() ...@@ -302,33 +315,41 @@ public void GetWithExpiryWrongTypeAsync()
RedisKey key = Me(); RedisKey key = Me();
db.KeyDelete(key); db.KeyDelete(key);
db.SetAdd(key, "abc"); db.SetAdd(key, "abc");
try Assert.Throws<RedisServerException>(() =>
{ {
var async = db.Wait(db.StringGetWithExpiryAsync(key)); try
} {
catch(AggregateException ex) var async = db.Wait(db.StringGetWithExpiryAsync(key));
{ }
throw ex.InnerExceptions[0]; catch (AggregateException ex)
} {
Assert.Fail(); throw ex.InnerExceptions[0];
}
Assert.Fail();
},
"A null key is not valid in this context");
} }
} }
[Test] [Test]
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")]
public void GetWithExpiryWrongTypeSync() public void GetWithExpiryWrongTypeSync()
{ {
using (var conn = Create()) Assert.Throws<RedisServerException>(() =>
{ {
var db = conn.GetDatabase(); using (var conn = Create())
RedisKey key = Me(); {
db.KeyDelete(key); var db = conn.GetDatabase();
db.SetAdd(key, "abc"); RedisKey key = Me();
db.StringGetWithExpiry(key); db.KeyDelete(key);
Assert.Fail(); db.SetAdd(key, "abc");
} db.StringGetWithExpiry(key);
Assert.Fail();
}
},
"WRONGTYPE Operation against a key holding the wrong kind of value");
} }
#if FEATURE_BOOKSLEEVE
[Test] [Test]
[TestCase(true, true, ResultCompletionMode.ConcurrentIfContinuation)] [TestCase(true, true, ResultCompletionMode.ConcurrentIfContinuation)]
[TestCase(true, false, ResultCompletionMode.ConcurrentIfContinuation)] [TestCase(true, false, ResultCompletionMode.ConcurrentIfContinuation)]
...@@ -378,7 +399,7 @@ public void MassiveBulkOpsAsyncOldStyle(bool withContinuation, bool suspendFlush ...@@ -378,7 +399,7 @@ public void MassiveBulkOpsAsyncOldStyle(bool withContinuation, bool suspendFlush
completionMode, AsyncOpsQty / watch.Elapsed.TotalSeconds); completionMode, AsyncOpsQty / watch.Elapsed.TotalSeconds);
} }
} }
#endif
[Test] [Test]
[TestCase(true, 1)] [TestCase(true, 1)]
...@@ -423,6 +444,8 @@ public void MassiveBulkOpsSync(bool preserveOrder, int threads) ...@@ -423,6 +444,8 @@ public void MassiveBulkOpsSync(bool preserveOrder, int threads)
#endif #endif
} }
} }
#if FEATURE_BOOKSLEEVE
[Test] [Test]
[TestCase(ResultCompletionMode.Concurrent, 1)] [TestCase(ResultCompletionMode.Concurrent, 1)]
[TestCase(ResultCompletionMode.ConcurrentIfContinuation, 1)] [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 1)]
...@@ -462,6 +485,7 @@ public void MassiveBulkOpsSyncOldStyle(ResultCompletionMode completionMode, int ...@@ -462,6 +485,7 @@ public void MassiveBulkOpsSyncOldStyle(ResultCompletionMode completionMode, int
completionMode, threads, (workPerThread * threads) / timeTaken.TotalSeconds); completionMode, threads, (workPerThread * threads) / timeTaken.TotalSeconds);
} }
} }
#endif
[Test] [Test]
[TestCase(true, 1)] [TestCase(true, 1)]
......
using System; #if FEATURE_MOQ
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
...@@ -12,7 +12,8 @@ public sealed class BatchWrapperTests ...@@ -12,7 +12,8 @@ public sealed class BatchWrapperTests
private Mock<IBatch> mock; private Mock<IBatch> mock;
private BatchWrapper wrapper; private BatchWrapper wrapper;
[TestFixtureSetUp] //[TestFixtureSetUp]
[OneTimeSetUpAttribute]
public void Initialize() public void Initialize()
{ {
mock = new Mock<IBatch>(); mock = new Mock<IBatch>();
...@@ -27,3 +28,4 @@ public void Execute() ...@@ -27,3 +28,4 @@ public void Execute()
} }
} }
} }
#endif
\ No newline at end of file
using System.Linq; using NUnit.Framework;
using NUnit.Framework;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
......
This diff is collapsed.
...@@ -125,13 +125,31 @@ public void ClientName() ...@@ -125,13 +125,31 @@ public void ClientName()
} }
[Test] [Test]
[ExpectedException(typeof(RedisCommandException), ExpectedMessage = "This operation has been disabled in the command-map and cannot be used: CONFIG")] 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() public void ReadConfigWithConfigDisabled()
{ {
using (var muxer = Create(allowAdmin: true, disabledCommands: new[] { "config", "info" })) using (var muxer = Create(allowAdmin: true, disabledCommands: new[] { "config", "info" }))
{ {
var conn = GetServer(muxer); var conn = GetServer(muxer);
var all = conn.ConfigGet(); Assert.Throws<RedisCommandException>(() =>
{
var all = conn.ConfigGet();
},
"This operation has been disabled in the command-map and cannot be used: CONFIG");
} }
} }
[Test] [Test]
...@@ -144,7 +162,11 @@ public void ReadConfig() ...@@ -144,7 +162,11 @@ public void ReadConfig()
var all = conn.ConfigGet(); var all = conn.ConfigGet();
Assert.IsTrue(all.Length > 0, "any"); Assert.IsTrue(all.Length > 0, "any");
#if !CORE_CLR
var pairs = all.ToDictionary(x => (string)x.Key, x => (string)x.Value, StringComparer.InvariantCultureIgnoreCase); 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.AreEqual(all.Length, pairs.Count);
Assert.IsTrue(pairs.ContainsKey("timeout"), "timeout"); Assert.IsTrue(pairs.ContainsKey("timeout"), "timeout");
...@@ -157,13 +179,13 @@ public void ReadConfig() ...@@ -157,13 +179,13 @@ public void ReadConfig()
} }
[Test] [Test]
public async void TestConfigureAsync() public async System.Threading.Tasks.Task TestConfigureAsync()
{ {
using(var muxer = Create()) using(var muxer = Create())
{ {
Thread.Sleep(1000); Thread.Sleep(1000);
Debug.WriteLine("About to reconfigure....."); Debug.WriteLine("About to reconfigure.....");
await muxer.ConfigureAsync(); await muxer.ConfigureAsync().ConfigureAwait(false);
Debug.WriteLine("Reconfigured"); Debug.WriteLine("Reconfigured");
} }
} }
......
using System; using System.Threading;
using System.Threading;
using NUnit.Framework; using NUnit.Framework;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
......
using NUnit.Framework; using NUnit.Framework;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
......
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<RedisConnectionException>(() => 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<RedisConnectionException>(() => muxer.GetDatabase().Ping());
var rde = (RedisConnectionException)ex.InnerException;
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<RedisConnectionException>(() => muxer.GetDatabase().Ping());
var rde = (RedisConnectionException)ex.InnerException;
Assert.That(rde.FailureType, Is.EqualTo(ConnectionFailureType.SocketFailure));
}
}
[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();
}
}
[Test]
public void TryGetAzureRoleInstanceIdNoThrow()
{
Assert.IsNull(ConnectionMultiplexer.TryGetAzureRoleInstanceIdNoThrow());
}
}
}
using System; #if FEATURE_MOQ
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Net; using System.Net;
...@@ -15,7 +16,8 @@ public sealed class DatabaseWrapperTests ...@@ -15,7 +16,8 @@ public sealed class DatabaseWrapperTests
private Mock<IDatabase> mock; private Mock<IDatabase> mock;
private DatabaseWrapper wrapper; private DatabaseWrapper wrapper;
[TestFixtureSetUp] //[TestFixtureSetUp]
[OneTimeSetUp]
public void Initialize() public void Initialize()
{ {
mock = new Mock<IDatabase>(); mock = new Mock<IDatabase>();
...@@ -289,10 +291,9 @@ public void KeyPersist() ...@@ -289,10 +291,9 @@ public void KeyPersist()
} }
[Test] [Test]
[ExpectedException(typeof(NotSupportedException))]
public void KeyRandom() public void KeyRandom()
{ {
wrapper.KeyRandom(); Assert.Throws<NotSupportedException>(() => wrapper.KeyRandom());
} }
[Test] [Test]
...@@ -933,3 +934,4 @@ public void StringSetRange() ...@@ -933,3 +934,4 @@ public void StringSetRange()
} }
} }
} }
#endif
\ No newline at end of file
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, new RedisCommand(), null, null, muxer.GetServerSnapshot());
Assert.Null(ex.InnerException);
}
}
[Test]
public void NullSnapshot()
{
var ex = ExceptionFactory.NoConnectionAvailable(true, new RedisCommand(), null, null, null);
Assert.Null(ex.InnerException);
}
[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, new RedisCommand(), null, null, muxer.GetServerSnapshot());
Assert.IsInstanceOf<RedisConnectionException>(ex);
Assert.IsInstanceOf<AggregateException>(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, new RedisCommand(), null, null, muxer.GetServerSnapshot());
Assert.IsInstanceOf<RedisConnectionException>(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, new RedisCommand(), null,muxer.GetServerSnapshot()[0], muxer.GetServerSnapshot());
Assert.IsInstanceOf<RedisConnectionException>(ex);
Assert.IsInstanceOf<Exception>(ex.InnerException);
Assert.That(muxer.GetServerSnapshot()[0].LastException, Is.EqualTo(ex.InnerException));
}
}
finally
{
SocketManager.ConnectCompletionType = CompletionType.Any;
ClearAmbientFailures();
}
}
}
}
...@@ -70,15 +70,15 @@ public async void IncrDecrFloatingPointAsync() ...@@ -70,15 +70,15 @@ public async void IncrDecrFloatingPointAsync()
double sum = 0; double sum = 0;
foreach (var value in incr) foreach (var value in incr)
{ {
await db.StringIncrementAsync(key, value); await db.StringIncrementAsync(key, value).ConfigureAwait(false);
sum += value; sum += value;
} }
foreach (var value in decr) foreach (var value in decr)
{ {
await db.StringDecrementAsync(key, value); await db.StringDecrementAsync(key, value).ConfigureAwait(false);
sum -= value; sum -= value;
} }
var val = (double)await db.StringGetAsync(key); var val = (double)await db.StringGetAsync(key).ConfigureAwait(false);
Assert.IsTrue(Within(sum, val, 0.0001)); Assert.IsTrue(Within(sum, val, 0.0001));
} }
...@@ -146,15 +146,15 @@ public async void HashIncrDecrFloatingPointAsync() ...@@ -146,15 +146,15 @@ public async void HashIncrDecrFloatingPointAsync()
double sum = 0; double sum = 0;
foreach (var value in incr) foreach (var value in incr)
{ {
await db.HashIncrementAsync(key, field, value); await db.HashIncrementAsync(key, field, value).ConfigureAwait(false);
sum += value; sum += value;
} }
foreach (var value in decr) foreach (var value in decr)
{ {
await db.HashDecrementAsync(key, field, value); await db.HashDecrementAsync(key, field, value).ConfigureAwait(false);
sum -= value; sum -= value;
} }
var val = (double)await db.HashGetAsync(key, field); var val = (double)await db.HashGetAsync(key, field).ConfigureAwait(false);
Assert.IsTrue(Within(sum, val, 0.0001)); Assert.IsTrue(Within(sum, val, 0.0001));
} }
......
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis;
namespace StackExchange.Redis.Tests.Issues namespace StackExchange.Redis.Tests.Issues
{ {
...@@ -32,7 +27,7 @@ public void ConfigurationOptions_UnspecifiedDefaultDb() ...@@ -32,7 +27,7 @@ public void ConfigurationOptions_UnspecifiedDefaultDb()
var log = new StringWriter(); var log = new StringWriter();
try try
{ {
using (var conn = ConnectionMultiplexer.Connect(string.Format("{0}:{1}", PrimaryServer, PrimaryPort), log)) { using (var conn = ConnectionMultiplexer.Connect($"{PrimaryServer}:{PrimaryPort}", log)) {
var db = conn.GetDatabase(); var db = conn.GetDatabase();
Assert.AreEqual(0, db.Database); Assert.AreEqual(0, db.Database);
} }
...@@ -49,7 +44,7 @@ public void ConfigurationOptions_SpecifiedDefaultDb() ...@@ -49,7 +44,7 @@ public void ConfigurationOptions_SpecifiedDefaultDb()
var log = new StringWriter(); var log = new StringWriter();
try try
{ {
using (var conn = ConnectionMultiplexer.Connect(string.Format("{0}:{1},defaultDatabase=3", PrimaryServer, PrimaryPort), log)) { using (var conn = ConnectionMultiplexer.Connect($"{PrimaryServer}:{PrimaryPort},defaultDatabase=3", log)) {
var db = conn.GetDatabase(); var db = conn.GetDatabase();
Assert.AreEqual(3, db.Database); Assert.AreEqual(3, db.Database);
} }
......
using System; namespace StackExchange.Redis.Tests.Issues
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackExchange.Redis.Tests.Issues
{ {
class Issue118 class Issue118
{ {
......
...@@ -23,15 +23,21 @@ public void UnkonwnKeywordHandling_Ignore() ...@@ -23,15 +23,21 @@ public void UnkonwnKeywordHandling_Ignore()
{ {
var options = ConfigurationOptions.Parse("ssl2=true", true); var options = ConfigurationOptions.Parse("ssl2=true", true);
} }
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")] [Test]
public void UnkonwnKeywordHandling_ExplicitFail() public void UnkonwnKeywordHandling_ExplicitFail()
{ {
var options = ConfigurationOptions.Parse("ssl2=true", false); Assert.Throws<ArgumentException>(() => {
var options = ConfigurationOptions.Parse("ssl2=true", false);
},
"Keyword 'ssl2' is not supported");
} }
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")] [Test]
public void UnkonwnKeywordHandling_ImplicitFail() public void UnkonwnKeywordHandling_ImplicitFail()
{ {
var options = ConfigurationOptions.Parse("ssl2=true"); Assert.Throws<ArgumentException>(() => {
var options = ConfigurationOptions.Parse("ssl2=true");
},
"Keyword 'ssl2' is not supported");
} }
} }
} }
using System; using System.Diagnostics;
using System.Diagnostics;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
......
...@@ -9,7 +9,7 @@ public class SO25567566 : TestBase ...@@ -9,7 +9,7 @@ public class SO25567566 : TestBase
{ {
protected override string GetConfiguration() protected override string GetConfiguration()
{ {
return "127.0.0.1"; return "127.0.0.1:6379";
} }
[Test] [Test]
public async void Execute() public async void Execute()
...@@ -18,7 +18,7 @@ public async void Execute() ...@@ -18,7 +18,7 @@ public async void Execute()
{ {
for(int i = 0; i < 100; i++) for(int i = 0; i < 100; i++)
{ {
Assert.AreEqual("ok", await DoStuff(conn)); Assert.AreEqual("ok", await DoStuff(conn).ConfigureAwait(false));
} }
} }
...@@ -30,13 +30,13 @@ private async Task<string> DoStuff(ConnectionMultiplexer conn) ...@@ -30,13 +30,13 @@ private async Task<string> DoStuff(ConnectionMultiplexer conn)
var timeout = Task.Delay(5000); var timeout = Task.Delay(5000);
var len = db.ListLengthAsync("list"); var len = db.ListLengthAsync("list");
if (await Task.WhenAny(timeout, len) != len) if (await Task.WhenAny(timeout, len).ConfigureAwait(false) != len)
{ {
return "Timeout getting length"; return "Timeout getting length";
} }
if ((await len) == 0) if ((await len.ConfigureAwait(false)) == 0)
{ {
db.ListRightPush("list", "foo", flags: CommandFlags.FireAndForget); db.ListRightPush("list", "foo", flags: CommandFlags.FireAndForget);
} }
...@@ -48,14 +48,14 @@ private async Task<string> DoStuff(ConnectionMultiplexer conn) ...@@ -48,14 +48,14 @@ private async Task<string> DoStuff(ConnectionMultiplexer conn)
var exec = tran.ExecuteAsync(); var exec = tran.ExecuteAsync();
// SWAP THESE TWO // SWAP THESE TWO
bool ok = await Task.WhenAny(exec, timeout) == exec; bool ok = await Task.WhenAny(exec, timeout).ConfigureAwait(false) == exec;
//bool ok = true; //bool ok = true;
if (ok) if (ok)
{ {
if (await exec) if (await exec.ConfigureAwait(false))
{ {
await Task.WhenAll(x, y, z); await Task.WhenAll(x, y, z).ConfigureAwait(false);
var db2 = conn.GetDatabase(); var db2 = conn.GetDatabase();
db2.HashGet("hash", "whatever"); db2.HashGet("hash", "whatever");
......
...@@ -10,7 +10,7 @@ namespace StackExchange.Redis.Tests ...@@ -10,7 +10,7 @@ namespace StackExchange.Redis.Tests
public class Locking : TestBase public class Locking : TestBase
{ {
[Test] [Test]
[TestCaseSource("TestModes")] [TestCaseSource(nameof(TestModes))]
public void AggressiveParallel(TestMode testMode) public void AggressiveParallel(TestMode testMode)
{ {
int count = 2; int count = 2;
...@@ -127,12 +127,12 @@ public enum TestMode ...@@ -127,12 +127,12 @@ public enum TestMode
NoMultiExec, NoMultiExec,
Twemproxy Twemproxy
} }
public IEnumerable<TestMode> TestModes() public static IEnumerable<TestMode> TestModes()
{ {
return (TestMode[])Enum.GetValues(typeof(TestMode)); return (TestMode[])Enum.GetValues(typeof(TestMode));
} }
[Test] [Test]
[TestCaseSource("TestModes")] [TestCaseSource(nameof(TestModes))]
public void TakeLockAndExtend(TestMode mode) public void TakeLockAndExtend(TestMode mode)
{ {
bool withTran = mode == TestMode.MultiExec; bool withTran = mode == TestMode.MultiExec;
...@@ -219,7 +219,7 @@ public void TakeLockAndExtend(TestMode mode) ...@@ -219,7 +219,7 @@ public void TakeLockAndExtend(TestMode mode)
[Test] [Test]
[TestCaseSource("TestModes")] [TestCaseSource(nameof(TestModes))]
public void TestBasicLockNotTaken(TestMode testMode) public void TestBasicLockNotTaken(TestMode testMode)
{ {
using (var conn = Create(testMode)) using (var conn = Create(testMode))
...@@ -249,7 +249,7 @@ public void TestBasicLockNotTaken(TestMode testMode) ...@@ -249,7 +249,7 @@ public void TestBasicLockNotTaken(TestMode testMode)
} }
[Test] [Test]
[TestCaseSource("TestModes")] [TestCaseSource(nameof(TestModes))]
public void TestBasicLockTaken(TestMode testMode) public void TestBasicLockTaken(TestMode testMode)
{ {
using (var conn = Create(testMode)) using (var conn = Create(testMode))
......
...@@ -14,16 +14,19 @@ protected override string GetConfiguration() ...@@ -14,16 +14,19 @@ protected override string GetConfiguration()
return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword; return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword;
} }
[Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Command cannot be issued to a slave: FLUSHDB")] [Test]
public void CannotFlushSlave() public void CannotFlushSlave()
{ {
ConfigurationOptions config = GetMasterSlaveConfig(); Assert.Throws<RedisCommandException>(() => {
using (var conn = ConnectionMultiplexer.Connect(config)) ConfigurationOptions config = GetMasterSlaveConfig();
{ using (var conn = ConnectionMultiplexer.Connect(config))
var servers = Array.ConvertAll(conn.GetEndPoints(), e => conn.GetServer(e)); {
var slave = servers.First(x => x.IsSlave); var servers = conn.GetEndPoints().Select(e => conn.GetServer(e));
slave.FlushDatabase(); var slave = servers.First(x => x.IsSlave);
} slave.FlushDatabase();
}
},
"Command cannot be issued to a slave: FLUSHDB");
} }
[Test] [Test]
......
...@@ -17,7 +17,7 @@ public class Naming ...@@ -17,7 +17,7 @@ public class Naming
public void CheckSignatures(Type type, bool isAsync) public void CheckSignatures(Type type, bool isAsync)
{ {
// check that all methods and interfaces look appropriate for their sync/async nature // check that all methods and interfaces look appropriate for their sync/async nature
CheckName(type, isAsync); CheckName(type.GetTypeInfo(), isAsync);
var members = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); var members = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
foreach(var member in members) foreach(var member in members)
{ {
...@@ -29,9 +29,9 @@ public void CheckSignatures(Type type, bool isAsync) ...@@ -29,9 +29,9 @@ public void CheckSignatures(Type type, bool isAsync)
[Test] [Test]
public void ShowReadOnlyOperations() public void ShowReadOnlyOperations()
{ {
var msg = typeof(ConnectionMultiplexer).Assembly.GetType("StackExchange.Redis.Message"); var msg = typeof(ConnectionMultiplexer).GetTypeInfo().Assembly.GetType("StackExchange.Redis.Message");
Assert.IsNotNull(msg, "Message"); Assert.IsNotNull(msg, "Message");
var cmd = typeof(ConnectionMultiplexer).Assembly.GetType("StackExchange.Redis.RedisCommand"); var cmd = typeof(ConnectionMultiplexer).GetTypeInfo().Assembly.GetType("StackExchange.Redis.RedisCommand");
Assert.IsNotNull(cmd, "RedisCommand"); Assert.IsNotNull(cmd, "RedisCommand");
var method = msg.GetMethod("IsMasterOnly", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); var method = msg.GetMethod("IsMasterOnly", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
Assert.IsNotNull(method, "IsMasterOnly"); Assert.IsNotNull(method, "IsMasterOnly");
...@@ -89,7 +89,7 @@ static bool UsesKey(Type type) ...@@ -89,7 +89,7 @@ static bool UsesKey(Type type)
{ {
if (UsesKey(type.GetElementType())) return true; if (UsesKey(type.GetElementType())) return true;
} }
if(type.IsGenericType) // KVP, etc if(type.GetTypeInfo().IsGenericType) // KVP, etc
{ {
var args = type.GetGenericArguments(); var args = type.GetGenericArguments();
if (args.Any(UsesKey)) return true; if (args.Any(UsesKey)) return true;
...@@ -139,7 +139,7 @@ public void CheckSyncAsyncMethodsMatch(Type from, Type to) ...@@ -139,7 +139,7 @@ public void CheckSyncAsyncMethodsMatch(Type from, Type to)
{ {
huntType = null; huntType = null;
} }
else if (method.ReturnType.IsSubclassOf(typeof(Task))) else if (method.ReturnType.GetTypeInfo().IsSubclassOf(typeof(Task)))
{ {
huntType = method.ReturnType.GetGenericArguments()[0]; huntType = method.ReturnType.GetGenericArguments()[0];
} }
...@@ -148,9 +148,14 @@ public void CheckSyncAsyncMethodsMatch(Type from, Type to) ...@@ -148,9 +148,14 @@ public void CheckSyncAsyncMethodsMatch(Type from, Type to)
huntType = typeof(Task<>).MakeGenericType(method.ReturnType); huntType = typeof(Task<>).MakeGenericType(method.ReturnType);
} }
var pFrom = method.GetParameters(); var pFrom = method.GetParameters();
Type[] args = Array.ConvertAll(pFrom, x => x.ParameterType); Type[] args = pFrom.Select(x => x.ParameterType).ToArray();
Assert.AreEqual(typeof(CommandFlags), args.Last()); Assert.AreEqual(typeof(CommandFlags), args.Last());
#if !CORE_CLR
var found = to.GetMethod(huntName, flags, null, method.CallingConvention, args, null); 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); Assert.IsNotNull(found, "Found " + name + ", no " + huntName);
var pTo = found.GetParameters(); var pTo = found.GetParameters();
...@@ -166,16 +171,24 @@ public void CheckSyncAsyncMethodsMatch(Type from, Type to) ...@@ -166,16 +171,24 @@ public void CheckSyncAsyncMethodsMatch(Type from, Type to)
Console.WriteLine("Validated: {0} ({1} methods)", from.Name, count); Console.WriteLine("Validated: {0} ({1} methods)", from.Name, count);
} }
static readonly Type ignoreType = typeof(ConnectionMultiplexer).Assembly.GetType("StackExchange.Redis.IgnoreNamePrefixAttribute"); static readonly Type ignoreType = typeof(ConnectionMultiplexer).GetTypeInfo().Assembly.GetType("StackExchange.Redis.IgnoreNamePrefixAttribute");
void CheckMethod(MethodInfo method, bool isAsync) void CheckMethod(MethodInfo method, bool isAsync)
{ {
#if DEBUG #if DEBUG
#if !CORE_CLR
bool ignorePrefix = ignoreType != null && Attribute.IsDefined(method, ignoreType); bool ignorePrefix = ignoreType != null && Attribute.IsDefined(method, ignoreType);
if(ignorePrefix) #else
bool ignorePrefix = ignoreType != null && method.IsDefined(ignoreType);
#endif
if (ignorePrefix)
{ {
#if !CORE_CLR
Attribute attrib = Attribute.GetCustomAttribute(method, ignoreType); Attribute attrib = Attribute.GetCustomAttribute(method, ignoreType);
if((bool)attrib.GetType().GetProperty("IgnoreEntireMethod").GetValue(attrib)) #else
Attribute attrib = method.GetCustomAttribute(ignoreType);
#endif
if ((bool)attrib.GetType().GetProperty("IgnoreEntireMethod").GetValue(attrib))
{ {
return; return;
} }
...@@ -212,4 +225,34 @@ void CheckName(MemberInfo member, bool isAsync) ...@@ -212,4 +225,34 @@ void CheckName(MemberInfo member, bool isAsync)
else Assert.IsFalse(member.Name.EndsWith("Async"), member.Name + ":Name - don't 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
}
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; #if CORE_CLR
using System.Reflection;
#endif
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using System.Threading; using System.Threading;
...@@ -445,7 +447,7 @@ public void LowAllocationEnumerable() ...@@ -445,7 +447,7 @@ public void LowAllocationEnumerable()
.ContinueWith( .ContinueWith(
async _ => async _ =>
{ {
return (string)(await db.StringGetAsync("foo" + i)); return (string)(await db.StringGetAsync("foo" + i).ConfigureAwait(false));
} }
); );
...@@ -456,11 +458,11 @@ public void LowAllocationEnumerable() ...@@ -456,11 +458,11 @@ public void LowAllocationEnumerable()
conn.WaitAll(allTasks.ToArray()); conn.WaitAll(allTasks.ToArray());
var res = conn.FinishProfiling(profiler.MyContext); var res = conn.FinishProfiling(profiler.MyContext);
Assert.IsTrue(res.GetType().IsValueType); Assert.IsTrue(res.GetType().GetTypeInfo().IsValueType);
using(var e = res.GetEnumerator()) using(var e = res.GetEnumerator())
{ {
Assert.IsTrue(e.GetType().IsValueType); Assert.IsTrue(e.GetType().GetTypeInfo().IsValueType);
Assert.IsTrue(e.MoveNext()); Assert.IsTrue(e.MoveNext());
var i = e.Current; var i = e.Current;
......
#if NUNITLITE
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
...@@ -38,17 +38,17 @@ public void ExplicitPublishMode() ...@@ -38,17 +38,17 @@ public void ExplicitPublishMode()
Thread.Sleep(1000); Thread.Sleep(1000);
pub.Publish("abcd", "efg"); pub.Publish("abcd", "efg");
Thread.Sleep(500); Thread.Sleep(500);
Assert.AreEqual(0, Thread.VolatileRead(ref a), "a1"); Assert.AreEqual(0, VolatileWrapper.Read(ref a), "a1");
Assert.AreEqual(1, Thread.VolatileRead(ref b), "b1"); Assert.AreEqual(1, VolatileWrapper.Read(ref b), "b1");
Assert.AreEqual(1, Thread.VolatileRead(ref c), "c1"); Assert.AreEqual(1, VolatileWrapper.Read(ref c), "c1");
Assert.AreEqual(1, Thread.VolatileRead(ref d), "d1"); Assert.AreEqual(1, VolatileWrapper.Read(ref d), "d1");
pub.Publish("*bcd", "efg"); pub.Publish("*bcd", "efg");
Thread.Sleep(500); Thread.Sleep(500);
Assert.AreEqual(1, Thread.VolatileRead(ref a), "a2"); Assert.AreEqual(1, VolatileWrapper.Read(ref a), "a2");
//Assert.AreEqual(1, Thread.VolatileRead(ref b), "b2"); //Assert.AreEqual(1, VolatileWrapper.Read(ref b), "b2");
//Assert.AreEqual(1, Thread.VolatileRead(ref c), "c2"); //Assert.AreEqual(1, VolatileWrapper.Read(ref c), "c2");
//Assert.AreEqual(1, Thread.VolatileRead(ref d), "d2"); //Assert.AreEqual(1, VolatileWrapper.Read(ref d), "d2");
} }
} }
...@@ -101,7 +101,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC ...@@ -101,7 +101,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC
{ {
Assert.AreEqual(0, received.Count); Assert.AreEqual(0, received.Count);
} }
Assert.AreEqual(0, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(0, VolatileWrapper.Read(ref secondHandler));
var count = sub.Publish(pubChannel, "def"); var count = sub.Publish(pubChannel, "def");
Ping(muxer, pub, sub, 3); Ping(muxer, pub, sub, 3);
...@@ -110,7 +110,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC ...@@ -110,7 +110,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC
{ {
Assert.AreEqual(1, received.Count); Assert.AreEqual(1, received.Count);
} }
Assert.AreEqual(1, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(1, VolatileWrapper.Read(ref secondHandler));
// unsubscribe from first; should still see second // unsubscribe from first; should still see second
sub.Unsubscribe(subChannel, handler1); sub.Unsubscribe(subChannel, handler1);
...@@ -120,7 +120,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC ...@@ -120,7 +120,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC
{ {
Assert.AreEqual(1, received.Count); Assert.AreEqual(1, received.Count);
} }
Assert.AreEqual(2, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(2, VolatileWrapper.Read(ref secondHandler));
Assert.AreEqual(1, count); Assert.AreEqual(1, count);
// unsubscribe from second; should see nothing this time // unsubscribe from second; should see nothing this time
...@@ -131,7 +131,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC ...@@ -131,7 +131,7 @@ public void TestBasicPubSub(bool preserveOrder, string channelPrefix, bool wildC
{ {
Assert.AreEqual(1, received.Count); Assert.AreEqual(1, received.Count);
} }
Assert.AreEqual(2, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(2, VolatileWrapper.Read(ref secondHandler));
Assert.AreEqual(0, count); Assert.AreEqual(0, count);
} }
} }
...@@ -172,7 +172,7 @@ public void TestBasicPubSubFireAndForget(bool preserveOrder) ...@@ -172,7 +172,7 @@ public void TestBasicPubSubFireAndForget(bool preserveOrder)
{ {
Assert.AreEqual(0, received.Count); Assert.AreEqual(0, received.Count);
} }
Assert.AreEqual(0, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(0, VolatileWrapper.Read(ref secondHandler));
Ping(muxer, pub, sub); Ping(muxer, pub, sub);
var count = sub.Publish(key, "def", CommandFlags.FireAndForget); var count = sub.Publish(key, "def", CommandFlags.FireAndForget);
Ping(muxer, pub, sub); Ping(muxer, pub, sub);
...@@ -181,7 +181,7 @@ public void TestBasicPubSubFireAndForget(bool preserveOrder) ...@@ -181,7 +181,7 @@ public void TestBasicPubSubFireAndForget(bool preserveOrder)
{ {
Assert.AreEqual(1, received.Count); Assert.AreEqual(1, received.Count);
} }
Assert.AreEqual(1, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(1, VolatileWrapper.Read(ref secondHandler));
sub.Unsubscribe(key); sub.Unsubscribe(key);
count = sub.Publish(key, "ghi", CommandFlags.FireAndForget); count = sub.Publish(key, "ghi", CommandFlags.FireAndForget);
...@@ -247,7 +247,7 @@ public void TestPatternPubSub(bool preserveOrder) ...@@ -247,7 +247,7 @@ public void TestPatternPubSub(bool preserveOrder)
{ {
Assert.AreEqual(0, received.Count); Assert.AreEqual(0, received.Count);
} }
Assert.AreEqual(0, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(0, VolatileWrapper.Read(ref secondHandler));
var count = sub.Publish("abc", "def"); var count = sub.Publish("abc", "def");
Ping(muxer, pub, sub); Ping(muxer, pub, sub);
...@@ -256,7 +256,7 @@ public void TestPatternPubSub(bool preserveOrder) ...@@ -256,7 +256,7 @@ public void TestPatternPubSub(bool preserveOrder)
{ {
Assert.AreEqual(1, received.Count); Assert.AreEqual(1, received.Count);
} }
Assert.AreEqual(1, Thread.VolatileRead(ref secondHandler)); Assert.AreEqual(1, VolatileWrapper.Read(ref secondHandler));
sub.Unsubscribe("a*c"); sub.Unsubscribe("a*c");
count = sub.Publish("abc", "ghi"); count = sub.Publish("abc", "ghi");
...@@ -392,7 +392,7 @@ public void SubscriptionsSurviveConnectionFailure() ...@@ -392,7 +392,7 @@ public void SubscriptionsSurviveConnectionFailure()
}); });
sub.Publish(channel, "abc"); sub.Publish(channel, "abc");
sub.Ping(); sub.Ping();
Assert.AreEqual(1, Thread.VolatileRead(ref counter), "counter"); Assert.AreEqual(1, VolatileWrapper.Read(ref counter), "counter");
var server = GetServer(muxer); var server = GetServer(muxer);
Assert.AreEqual(1, server.GetCounters().Subscription.SocketCount, "sockets"); Assert.AreEqual(1, server.GetCounters().Subscription.SocketCount, "sockets");
...@@ -408,8 +408,20 @@ public void SubscriptionsSurviveConnectionFailure() ...@@ -408,8 +408,20 @@ public void SubscriptionsSurviveConnectionFailure()
#endif #endif
sub.Publish(channel, "abc"); sub.Publish(channel, "abc");
sub.Ping(); sub.Ping();
Assert.AreEqual(2, Thread.VolatileRead(ref counter), "counter"); 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
}
}
} }
...@@ -158,7 +158,7 @@ public void TestCallByHash() ...@@ -158,7 +158,7 @@ public void TestCallByHash()
var db = conn.GetDatabase(); var db = conn.GetDatabase();
RedisKey[] keys = { Me() }; RedisKey[] keys = { Me() };
string hexHash = string.Concat(Array.ConvertAll(hash, x => x.ToString("X2"))); string hexHash = string.Concat(hash.Select(x => x.ToString("X2")));
Assert.AreEqual("2BAB3B661081DB58BD2341920E0BA7CF5DC77B25", hexHash); Assert.AreEqual("2BAB3B661081DB58BD2341920E0BA7CF5DC77B25", hexHash);
db.ScriptEvaluate(hexHash, keys); db.ScriptEvaluate(hexHash, keys);
......
...@@ -68,14 +68,16 @@ public void Connect() ...@@ -68,14 +68,16 @@ public void Connect()
[Test] [Test]
[TestCase("wrong")] [TestCase("wrong")]
[TestCase("")] [TestCase("")]
[ExpectedException(typeof(RedisConnectionException), ExpectedMessage = "No connection is available to service this operation: PING")]
public void ConnectWithWrongPassword(string password) public void ConnectWithWrongPassword(string password)
{ {
SetExpectedAmbientFailureCount(-1); Assert.Throws<RedisConnectionException>(() => {
using (var server = Create(password: password, checkConnect: false)) SetExpectedAmbientFailureCount(-1);
{ using (var server = Create(password: password, checkConnect: false))
server.GetDatabase().Ping(); {
} server.GetDatabase().Ping();
}
},
"No connection is available to service this operation: PING");
} }
} }
} }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
[TestFixture, Ignore] [TestFixture, Ignore("reason?")]
public class Sentinel public class Sentinel
{ {
// TODO fill in these constants before running tests // TODO fill in these constants before running tests
...@@ -100,7 +100,7 @@ public void SentinelSlavesTest() ...@@ -100,7 +100,7 @@ public void SentinelSlavesTest()
} }
} }
[Test, Ignore] [Test, Ignore("reason?")]
public void SentinelFailoverTest() public void SentinelFailoverTest()
{ {
Server.SentinelFailover(ServiceName); Server.SentinelFailover(ServiceName);
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>3b8bd8f1-8bfc-4d8c-b4da-25ffaf3d1dbe</ProjectGuid>
<RootNamespace>StackExchange.Redis.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
\ No newline at end of file
...@@ -28,7 +28,7 @@ static TaskCompletionSource<T> Create<T>(SourceOrign origin) ...@@ -28,7 +28,7 @@ static TaskCompletionSource<T> Create<T>(SourceOrign origin)
case SourceOrign.NewTCS: return new TaskCompletionSource<T>(); case SourceOrign.NewTCS: return new TaskCompletionSource<T>();
case SourceOrign.Create: return TaskSource.Create<T>(null); case SourceOrign.Create: return TaskSource.Create<T>(null);
case SourceOrign.CreateDenyExec: return TaskSource.CreateDenyExecSync<T>(null); case SourceOrign.CreateDenyExec: return TaskSource.CreateDenyExecSync<T>(null);
default: throw new ArgumentOutOfRangeException("origin"); default: throw new ArgumentOutOfRangeException(nameof(origin));
} }
} }
[Test] [Test]
...@@ -78,7 +78,7 @@ public enum AttachMode ...@@ -78,7 +78,7 @@ public enum AttachMode
} }
class AwaitState class AwaitState
{ {
public int Thread { get { return continuationThread; } } public int Thread => continuationThread;
volatile int continuationThread = -1; volatile int continuationThread = -1;
private ManualResetEventSlim evt = new ManualResetEventSlim(); private ManualResetEventSlim evt = new ManualResetEventSlim();
public void Wait() public void Wait()
...@@ -99,7 +99,7 @@ public void Attach(Task task, AttachMode attachMode) ...@@ -99,7 +99,7 @@ public void Attach(Task task, AttachMode attachMode)
DoAwait(task); DoAwait(task);
break; break;
default: default:
throw new ArgumentOutOfRangeException("attachMode"); throw new ArgumentOutOfRangeException(nameof(attachMode));
} }
} }
private void Continue(Task task) private void Continue(Task task)
...@@ -109,7 +109,7 @@ private void Continue(Task task) ...@@ -109,7 +109,7 @@ private void Continue(Task task)
} }
private async void DoAwait(Task task) private async void DoAwait(Task task)
{ {
await task; await task.ConfigureAwait(false);
continuationThread = Environment.CurrentManagedThreadId; continuationThread = Environment.CurrentManagedThreadId;
evt.Set(); evt.Set();
} }
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
#if FEATURE_BOOKSLEEVE
using BookSleeve; using BookSleeve;
#endif
using NUnit.Framework; using NUnit.Framework;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
...@@ -48,6 +50,9 @@ static TestBase() ...@@ -48,6 +50,9 @@ static TestBase()
{ {
Console.WriteLine("Unobserved: " + args.Exception); Console.WriteLine("Unobserved: " + args.Exception);
args.SetObserved(); args.SetObserved();
#if CORE_CLR
if (IgnorableExceptionPredicates.Any(predicate => predicate(args.Exception.InnerException))) return;
#endif
Interlocked.Increment(ref failCount); Interlocked.Increment(ref failCount);
lock (exceptions) lock (exceptions)
{ {
...@@ -55,6 +60,15 @@ static TestBase() ...@@ -55,6 +60,15 @@ static TestBase()
} }
}; };
} }
#if CORE_CLR
static Func<Exception, bool>[] IgnorableExceptionPredicates = new Func<Exception, bool>[]
{
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) protected void OnConnectionFailed(object sender, ConnectionFailedEventArgs e)
{ {
Interlocked.Increment(ref failCount); Interlocked.Increment(ref failCount);
...@@ -221,6 +235,7 @@ protected static string Me([CallerMemberName] string caller = null) ...@@ -221,6 +235,7 @@ protected static string Me([CallerMemberName] string caller = null)
return caller; return caller;
} }
#if FEATURE_BOOKSLEEVE
protected static RedisConnection GetOldStyleConnection(bool open = true, bool allowAdmin = false, bool waitForOpen = false, int syncTimeout = 5000, int ioTimeout = 5000) 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); return GetOldStyleConnection(PrimaryServer, PrimaryPort, open, allowAdmin, waitForOpen, syncTimeout, ioTimeout);
...@@ -239,11 +254,11 @@ private static RedisConnection GetOldStyleConnection(string host, int port, bool ...@@ -239,11 +254,11 @@ private static RedisConnection GetOldStyleConnection(string host, int port, bool
} }
return conn; return conn;
} }
#endif
protected static TimeSpan RunConcurrent(Action work, int threads, int timeout = 10000, [CallerMemberName] string caller = null) protected static TimeSpan RunConcurrent(Action work, int threads, int timeout = 10000, [CallerMemberName] string caller = null)
{ {
if (work == null) throw new ArgumentNullException("work"); if (work == null) throw new ArgumentNullException(nameof(work));
if (threads < 1) throw new ArgumentOutOfRangeException("theads"); if (threads < 1) throw new ArgumentOutOfRangeException(nameof(threads));
if(string.IsNullOrWhiteSpace(caller)) caller = Me(); if(string.IsNullOrWhiteSpace(caller)) caller = Me();
Stopwatch watch = null; Stopwatch watch = null;
ManualResetEvent allDone = new ManualResetEvent(false); ManualResetEvent allDone = new ManualResetEvent(false);
...@@ -282,16 +297,28 @@ protected static TimeSpan RunConcurrent(Action work, int threads, int timeout = ...@@ -282,16 +297,28 @@ protected static TimeSpan RunConcurrent(Action work, int threads, int timeout =
} }
if (!allDone.WaitOne(timeout)) if (!allDone.WaitOne(timeout))
{ {
for(int i = 0; i < threads; i++) #if !CORE_CLR
for (int i = 0; i < threads; i++)
{ {
var thd = threadArr[i]; var thd = threadArr[i];
if (thd.IsAlive) thd.Abort(); if (thd.IsAlive) thd.Abort();
} }
#endif
throw new TimeoutException(); throw new TimeoutException();
} }
return watch.Elapsed; 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];
}
} }
} }
using System.Text; #if FEATURE_MOQ
using System.Text;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
...@@ -11,7 +12,7 @@ public sealed class TransactionWrapperTests ...@@ -11,7 +12,7 @@ public sealed class TransactionWrapperTests
private Mock<ITransaction> mock; private Mock<ITransaction> mock;
private TransactionWrapper wrapper; private TransactionWrapper wrapper;
[TestFixtureSetUp] [OneTimeSetUp]
public void Initialize() public void Initialize()
{ {
mock = new Mock<ITransaction>(); mock = new Mock<ITransaction>();
...@@ -89,3 +90,4 @@ public void Execute() ...@@ -89,3 +90,4 @@ public void Execute()
} }
} }
} }
#endif
\ No newline at end of file
This diff is collapsed.
...@@ -28,33 +28,39 @@ public void BlankPrefixYieldsSame_String() ...@@ -28,33 +28,39 @@ public void BlankPrefixYieldsSame_String()
Assert.AreSame(raw, prefixed); Assert.AreSame(raw, prefixed);
} }
} }
[Test, ExpectedException(typeof(ArgumentNullException))] [Test]
public void NullPrefixIsError_Bytes() public void NullPrefixIsError_Bytes()
{ {
using (var conn = Create()) Assert.Throws<ArgumentNullException>(() => {
{ using (var conn = Create())
var raw = conn.GetDatabase(1); {
var prefixed = raw.WithKeyPrefix((byte[])null); var raw = conn.GetDatabase(1);
} var prefixed = raw.WithKeyPrefix((byte[])null);
}
});
} }
[Test, ExpectedException(typeof(ArgumentNullException))] [Test]
public void NullPrefixIsError_String() public void NullPrefixIsError_String()
{ {
using (var conn = Create()) Assert.Throws<ArgumentNullException>(() => {
{ using (var conn = Create())
var raw = conn.GetDatabase(1); {
var prefixed = raw.WithKeyPrefix((string)null); var raw = conn.GetDatabase(1);
} var prefixed = raw.WithKeyPrefix((string)null);
}
});
} }
[Test, ExpectedException(typeof(ArgumentNullException))] [Test]
[TestCase("abc")] [TestCase("abc")]
[TestCase("")] [TestCase("")]
[TestCase(null)] [TestCase(null)]
public void NullDatabaseIsError(string prefix) public void NullDatabaseIsError(string prefix)
{ {
IDatabase raw = null; Assert.Throws<ArgumentNullException>(() => {
var prefixed = raw.WithKeyPrefix(prefix); IDatabase raw = null;
var prefixed = raw.WithKeyPrefix(prefix);
});
} }
[Test] [Test]
public void BasicSmokeTest() public void BasicSmokeTest()
......
using System; #if FEATURE_MOQ
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Net; using System.Net;
...@@ -15,7 +16,7 @@ public sealed class WrapperBaseTests ...@@ -15,7 +16,7 @@ public sealed class WrapperBaseTests
private Mock<IDatabaseAsync> mock; private Mock<IDatabaseAsync> mock;
private WrapperBase<IDatabaseAsync> wrapper; private WrapperBase<IDatabaseAsync> wrapper;
[TestFixtureSetUp] [OneTimeSetUp]
public void Initialize() public void Initialize()
{ {
mock = new Mock<IDatabaseAsync>(); mock = new Mock<IDatabaseAsync>();
...@@ -258,10 +259,11 @@ public void KeyPersistAsync() ...@@ -258,10 +259,11 @@ public void KeyPersistAsync()
} }
[Test] [Test]
[ExpectedException(typeof(NotSupportedException))]
public void KeyRandomAsync() public void KeyRandomAsync()
{ {
wrapper.KeyRandomAsync(); Assert.Throws<NotSupportedException>(() => {
wrapper.KeyRandomAsync();
});
} }
[Test] [Test]
...@@ -888,3 +890,4 @@ public void StringSetRangeAsync() ...@@ -888,3 +890,4 @@ public void StringSetRangeAsync()
} }
} }
} }
#endif
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
<packages> <packages>
<package id="BookSleeve" version="1.3.41" targetFramework="net45" /> <package id="BookSleeve" version="1.3.41" targetFramework="net45" />
<package id="Moq" version="4.2.1502.0911" targetFramework="net45" /> <package id="Moq" version="4.2.1502.0911" targetFramework="net45" />
<package id="NUnit" version="2.6.4" targetFramework="net45" /> <package id="NUnit" version="3.0.0" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
{
"version": "1.0.0-*",
"description": "StackExchange.Redis.Tests",
"dependencies": {
"StackExchange.Redis": {
"version": "1.1.*",
"target": "project"
}
},
"commands": {
"run": "StackExchange.Redis.Tests"
},
"buildOptions": {
"define": [ "NUNITLITE" ],
"emitEntryPoint": true
},
"runtimes": {
"win81-x64": {}
},
"frameworks": {
"netcoreapp1.0": {
"imports": [ "dnxcore50" ],
"buildOptions": {
"define": [ "PLAT_SAFE_CONTINUATIONS", "CORE_CLR" ]
},
"dependencies": {
"System.Console": "4.0.0-rc2-24027",
"System.Linq.Expressions": "4.0.11-rc2-24027",
"System.Reflection.Extensions": "4.0.1-rc2-24027",
"System.Threading.Tasks.Parallel": "4.0.1-rc2-24027",
"Microsoft.CSharp": "4.0.1-rc2-24027",
"nunitlite": "3.2.1"
}
}
}
}

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "BasicTest_dnxcore50", "BasicTest_dnxcore50\BasicTest_dnxcore50.xproj", "{9D83BABA-A92E-495F-BF63-DEB4F6B09355}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "StackExchange.Redis", "StackExchange.Redis\StackExchange.Redis.xproj", "{EF84877F-59BE-41BE-9013-E765AF0BB72E}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "StackExchange.Redis.StrongName", "StackExchange.Redis.StrongName\StackExchange.Redis.StrongName.xproj", "{46754D2A-AC16-4686-B113-3DB08ACF4269}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "StackExchange.Redis.Tests", "StackExchange.Redis.Tests\StackExchange.Redis.Tests.xproj", "{3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3AD17044-6BFF-4750-9AC2-2CA466375F2A}"
ProjectSection(SolutionItems) = preProject
NuGet.Config = NuGet.Config
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9D83BABA-A92E-495F-BF63-DEB4F6B09355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D83BABA-A92E-495F-BF63-DEB4F6B09355}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D83BABA-A92E-495F-BF63-DEB4F6B09355}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D83BABA-A92E-495F-BF63-DEB4F6B09355}.Release|Any CPU.Build.0 = Release|Any CPU
{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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF84877F-59BE-41BE-9013-E765AF0BB72E}.Release|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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46754D2A-AC16-4686-B113-3DB08ACF4269}.Release|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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B8BD8F1-8BFC-4D8C-B4DA-25FFAF3D1DBE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.md", "StackExchange.Redis\StackExchange.Redis.md.csproj", "{8EA21FC8-9CF7-438B-94F2-42E3810C3641}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8EA21FC8-9CF7-438B-94F2-42E3810C3641}.Debug|x86.ActiveCfg = Debug|x86
{8EA21FC8-9CF7-438B-94F2-42E3810C3641}.Debug|x86.Build.0 = Debug|x86
{8EA21FC8-9CF7-438B-94F2-42E3810C3641}.Release|x86.ActiveCfg = Release|x86
{8EA21FC8-9CF7-438B-94F2-42E3810C3641}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = StackExchange.Redis.md\StackExchange.Redis.md.csproj
EndGlobalSection
EndGlobal
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
<package> <package>
<metadata> <metadata>
<id>StackExchange.Redis</id> <id>StackExchange.Redis</id>
<version>1.0.0.0</version> <version>1.1.0.0</version>
<authors>Stack Exchange inc., marc.gravell</authors> <authors>Stack Exchange inc., marc.gravell</authors>
<owners>Stack Exchange inc., marc.gravell</owners> <owners>Stack Exchange inc., marc.gravell</owners>
<summary>Redis client library</summary> <summary>Redis client library</summary>
<description>High performance Redis client, incorporating both synchronous and asynchronous usage; the notional successor to BookSleeve. If you require a strong-named version, try StackExchange.Redis.StrongName</description> <description>High performance Redis client, incorporating both synchronous and asynchronous usage.</description>
<tags>Async Redis NoSQL Client Distributed Cache PubSub Messaging</tags> <tags>Async Redis NoSQL Client Distributed Cache PubSub Messaging</tags>
<language>en-US</language> <language>en-US</language>
<projectUrl>https://github.com/StackExchange/StackExchange.Redis</projectUrl> <projectUrl>https://github.com/StackExchange/StackExchange.Redis</projectUrl>
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<dependencies> <dependencies>
<group targetFramework="net40"> <group targetFramework="net40">
<dependency id="Microsoft.Bcl" version="1.1.9"/> <dependency id="Microsoft.Bcl" version="1.1.10"/>
<dependency id="Microsoft.Bcl.Async" version="1.0.168"/> <dependency id="Microsoft.Bcl.Async" version="1.0.168"/>
</group> </group>
<group targetFramework="net45"> <group targetFramework="net45">
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</metadata> </metadata>
<files> <files>
<file src="StackExchange.Redis_Net46\bin\Release\StackExchange.Redis*.*" target="lib\net46" /> <file src="StackExchange.Redis_Net46\bin\Release\StackExchange.Redis*.*" target="lib\net46" />
<file src="StackExchange.Redis\bin\Release\StackExchange.Redis*.*" target="lib\net45" /> <file src="StackExchange.Redis_Net45\bin\Release\StackExchange.Redis*.*" target="lib\net45" />
<file src="StackExchange.Redis_Net40\bin\Release\StackExchange.Redis*.*" target="lib\net40" /> <file src="StackExchange.Redis_Net40\bin\Release\StackExchange.Redis*.*" target="lib\net40" />
</files> </files>
</package> </package>
\ No newline at end of file
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.24606.1 VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis", "StackExchange.Redis\StackExchange.Redis.csproj", "{7CEC07F2-8C03-4C42-B048-738B215824C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.Tests", "StackExchange.Redis.Tests\StackExchange.Redis.Tests.csproj", "{19C00111-1328-4089-8565-94920B5B47F2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{D3090DF1-8760-4FE2-A595-4C8AC12854ED}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{D3090DF1-8760-4FE2-A595-4C8AC12854ED}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.nuget\packages.config = .nuget\packages.config .nuget\packages.config = .nuget\packages.config
...@@ -52,14 +48,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigratedBookSleeveTestSuite ...@@ -52,14 +48,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigratedBookSleeveTestSuite
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net40", "StackExchange.Redis_Net40\StackExchange.Redis_Net40.csproj", "{36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net40", "StackExchange.Redis_Net40\StackExchange.Redis_Net40.csproj", "{36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.StrongName", "StackExchange.Redis\StackExchange.Redis.StrongName.csproj", "{EBF46088-E318-4D32-9EFB-01EF130A4554}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net40.StrongName", "StackExchange.Redis_Net40\StackExchange.Redis_Net40.StrongName.csproj", "{75CED009-AAC6-4AC1-9C38-A0530619062D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net40.StrongName", "StackExchange.Redis_Net40\StackExchange.Redis_Net40.StrongName.csproj", "{75CED009-AAC6-4AC1-9C38-A0530619062D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net46", "StackExchange.Redis_Net46\StackExchange.Redis_Net46.csproj", "{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net46", "StackExchange.Redis_Net46\StackExchange.Redis_Net46.csproj", "{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net46.StrongName", "StackExchange.Redis_Net46\StackExchange.Redis_Net46.StrongName.csproj", "{8CE5D027-E332-42DD-BA54-16310DCD529C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net46.StrongName", "StackExchange.Redis_Net46\StackExchange.Redis_Net46.StrongName.csproj", "{8CE5D027-E332-42DD-BA54-16310DCD529C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net45", "StackExchange.Redis_Net45\StackExchange.Redis_Net45.csproj", "{7CEC07F2-8C03-4C42-B048-738B215824C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net45.StrongName", "StackExchange.Redis_Net45\StackExchange.Redis_Net45.StrongName.csproj", "{EBF46088-E318-4D32-9EFB-01EF130A4554}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.Tests_Net46", "StackExchange.Redis.Tests_Net46\StackExchange.Redis.Tests_Net46.csproj", "{19C00111-1328-4089-8565-94920B5B47F2}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
...@@ -69,26 +69,6 @@ Global ...@@ -69,26 +69,6 @@ Global
Verbose|Any CPU = Verbose|Any CPU Verbose|Any CPU = Verbose|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Log Output|Any CPU.Build.0 = Log Output|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Mono|Any CPU.Build.0 = Mono|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Release|Any CPU.Build.0 = Release|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Log Output|Any CPU.Build.0 = Log Output|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Mono|Any CPU.Build.0 = Mono|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Release|Any CPU.Build.0 = Release|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
{6756F911-BD09-4226-B597-67871DEB8ED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6756F911-BD09-4226-B597-67871DEB8ED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6756F911-BD09-4226-B597-67871DEB8ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU {6756F911-BD09-4226-B597-67871DEB8ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6756F911-BD09-4226-B597-67871DEB8ED5}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU {6756F911-BD09-4226-B597-67871DEB8ED5}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU
...@@ -139,16 +119,6 @@ Global ...@@ -139,16 +119,6 @@ Global
{36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}.Release|Any CPU.Build.0 = Release|Any CPU {36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}.Release|Any CPU.Build.0 = Release|Any CPU
{36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}.Verbose|Any CPU.ActiveCfg = Release|Any CPU {36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}.Verbose|Any CPU.ActiveCfg = Release|Any CPU
{36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}.Verbose|Any CPU.Build.0 = Release|Any CPU {36CAC6B6-2B88-447F-AA35-D4DAA5E4F2C7}.Verbose|Any CPU.Build.0 = Release|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Log Output|Any CPU.Build.0 = Log Output|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Mono|Any CPU.Build.0 = Mono|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Release|Any CPU.Build.0 = Release|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
{75CED009-AAC6-4AC1-9C38-A0530619062D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {75CED009-AAC6-4AC1-9C38-A0530619062D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75CED009-AAC6-4AC1-9C38-A0530619062D}.Debug|Any CPU.Build.0 = Debug|Any CPU {75CED009-AAC6-4AC1-9C38-A0530619062D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75CED009-AAC6-4AC1-9C38-A0530619062D}.Log Output|Any CPU.ActiveCfg = Release|Any CPU {75CED009-AAC6-4AC1-9C38-A0530619062D}.Log Output|Any CPU.ActiveCfg = Release|Any CPU
...@@ -179,6 +149,36 @@ Global ...@@ -179,6 +149,36 @@ Global
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Release|Any CPU.Build.0 = Release|Any CPU {8CE5D027-E332-42DD-BA54-16310DCD529C}.Release|Any CPU.Build.0 = Release|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Verbose|Any CPU.ActiveCfg = Mono|Any CPU {8CE5D027-E332-42DD-BA54-16310DCD529C}.Verbose|Any CPU.ActiveCfg = Mono|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Verbose|Any CPU.Build.0 = Mono|Any CPU {8CE5D027-E332-42DD-BA54-16310DCD529C}.Verbose|Any CPU.Build.0 = Mono|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Log Output|Any CPU.Build.0 = Log Output|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Mono|Any CPU.Build.0 = Mono|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Release|Any CPU.Build.0 = Release|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
{7CEC07F2-8C03-4C42-B048-738B215824C1}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Log Output|Any CPU.Build.0 = Log Output|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Mono|Any CPU.Build.0 = Mono|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Release|Any CPU.Build.0 = Release|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
{EBF46088-E318-4D32-9EFB-01EF130A4554}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Log Output|Any CPU.ActiveCfg = Log Output|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Log Output|Any CPU.Build.0 = Log Output|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Mono|Any CPU.Build.0 = Mono|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Release|Any CPU.Build.0 = Release|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
{19C00111-1328-4089-8565-94920B5B47F2}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
......
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OK/@EntryIndexedValue">OK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PONG/@EntryIndexedValue">PONG</s:String></wpf:ResourceDictionary>
\ No newline at end of file
using System; using System;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
...@@ -11,7 +10,7 @@ ...@@ -11,7 +10,7 @@
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("StackExchange.Redis")] [assembly: AssemblyProduct("StackExchange.Redis")]
[assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
...@@ -37,8 +36,4 @@ ...@@ -37,8 +36,4 @@
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0.0")]
[assembly:CLSCompliant(true)] [assembly: CLSCompliant(true)]
\ No newline at end of file
#if !STRONG_NAME
[assembly:InternalsVisibleTo("StackExchange.Redis.Tests")]
#endif
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{8EA21FC8-9CF7-438B-94F2-42E3810C3641}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>StackExchange.Redis.md</RootNamespace>
<AssemblyName>StackExchange.Redis.md</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;MONO</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<ConsolePause>false</ConsolePause>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<ConsolePause>false</ConsolePause>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>MONO</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StackExchange\Redis\Aggregate.cs" />
<Compile Include="StackExchange\Redis\Bitwise.cs" />
<Compile Include="StackExchange\Redis\ClientFlags.cs" />
<Compile Include="StackExchange\Redis\ClientInfo.cs" />
<Compile Include="StackExchange\Redis\ClusterConfiguration.cs" />
<Compile Include="StackExchange\Redis\CommandFlags.cs" />
<Compile Include="StackExchange\Redis\CommandMap.cs" />
<Compile Include="StackExchange\Redis\CommandTrace.cs" />
<Compile Include="StackExchange\Redis\CompletedDefaultTask.cs" />
<Compile Include="StackExchange\Redis\CompletionManager.cs" />
<Compile Include="StackExchange\Redis\Condition.cs" />
<Compile Include="StackExchange\Redis\ConfigurationOptions.cs" />
<Compile Include="StackExchange\Redis\ConnectionCounters.cs" />
<Compile Include="StackExchange\Redis\ConnectionFailedEventArgs.cs" />
<Compile Include="StackExchange\Redis\ConnectionFailureType.cs" />
<Compile Include="StackExchange\Redis\ConnectionMultiplexer.cs" />
<Compile Include="StackExchange\Redis\ConnectionMultiplexer.ReaderWriter.cs" />
<Compile Include="StackExchange\Redis\ConnectionType.cs" />
<Compile Include="StackExchange\Redis\DebuggingAids.cs" />
<Compile Include="StackExchange\Redis\EndPointCollection.cs" />
<Compile Include="StackExchange\Redis\EndPointEventArgs.cs" />
<Compile Include="StackExchange\Redis\ExceptionFactory.cs" />
<Compile Include="StackExchange\Redis\Exclude.cs" />
<Compile Include="StackExchange\Redis\ExportOptions.cs" />
<Compile Include="StackExchange\Redis\Format.cs" />
<Compile Include="StackExchange\Redis\HashSlotMovedEventArgs.cs" />
<Compile Include="StackExchange\Redis\IBatch.cs" />
<Compile Include="StackExchange\Redis\ICompletable.cs" />
<Compile Include="StackExchange\Redis\IDatabase.cs" />
<Compile Include="StackExchange\Redis\IDatabaseAsync.cs" />
<Compile Include="StackExchange\Redis\IMultiMessage.cs" />
<Compile Include="StackExchange\Redis\InternalErrorEventArgs.cs" />
<Compile Include="StackExchange\Redis\IRedis.cs" />
<Compile Include="StackExchange\Redis\IRedisAsync.cs" />
<Compile Include="StackExchange\Redis\IServer.cs" />
<Compile Include="StackExchange\Redis\ISubscriber.cs" />
<Compile Include="StackExchange\Redis\ITransaction.cs" />
<Compile Include="StackExchange\Redis\LoggingTextStream.cs" />
<Compile Include="StackExchange\Redis\Message.cs" />
<Compile Include="StackExchange\Redis\MessageCompletable.cs" />
<Compile Include="StackExchange\Redis\MessageQueue.cs" />
<Compile Include="StackExchange\Redis\Order.cs" />
<Compile Include="StackExchange\Redis\PhysicalBridge.cs" />
<Compile Include="StackExchange\Redis\PhysicalConnection.cs" />
<Compile Include="StackExchange\Redis\RawResult.cs" />
<Compile Include="StackExchange\Redis\RedisBase.cs" />
<Compile Include="StackExchange\Redis\RedisBatch.cs" />
<Compile Include="StackExchange\Redis\RedisChannel.cs" />
<Compile Include="StackExchange\Redis\RedisCommand.cs" />
<Compile Include="StackExchange\Redis\RedisDatabase.cs" />
<Compile Include="StackExchange\Redis\RedisErrorEventArgs.cs" />
<Compile Include="StackExchange\Redis\RedisFeatures.cs" />
<Compile Include="StackExchange\Redis\RedisKey.cs" />
<Compile Include="StackExchange\Redis\RedisLiterals.cs" />
<Compile Include="StackExchange\Redis\RedisResult.cs" />
<Compile Include="StackExchange\Redis\RedisServer.cs" />
<Compile Include="StackExchange\Redis\RedisSubscriber.cs" />
<Compile Include="StackExchange\Redis\RedisTransaction.cs" />
<Compile Include="StackExchange\Redis\RedisType.cs" />
<Compile Include="StackExchange\Redis\RedisValue.cs" />
<Compile Include="StackExchange\Redis\ReplicationChangeOptions.cs" />
<Compile Include="StackExchange\Redis\ResultBox.cs" />
<Compile Include="StackExchange\Redis\ResultProcessor.cs" />
<Compile Include="StackExchange\Redis\ResultType.cs" />
<Compile Include="StackExchange\Redis\SaveType.cs" />
<Compile Include="StackExchange\Redis\ServerCounters.cs" />
<Compile Include="StackExchange\Redis\ServerEndPoint.cs" />
<Compile Include="StackExchange\Redis\ServerSelectionStrategy.cs" />
<Compile Include="StackExchange\Redis\ServerType.cs" />
<Compile Include="StackExchange\Redis\SetOperation.cs" />
<Compile Include="StackExchange\Redis\ShutdownMode.cs" />
<Compile Include="StackExchange\Redis\SocketManager.cs" />
<Compile Include="StackExchange\Redis\SortType.cs" />
<Compile Include="StackExchange\Redis\StringSplits.cs" />
<Compile Include="StackExchange\Redis\TaskSource.cs" />
<Compile Include="StackExchange\Redis\When.cs" />
<Compile Include="StackExchange\Redis\HashEntry.cs" />
<Compile Include="StackExchange\Redis\ExtensionMethods.cs" />
<Compile Include="StackExchange\Redis\SocketManager.NoPoll.cs" />
<Compile Include="StackExchange\Redis\SocketManager.Poll.cs" />
<Compile Include="StackExchange\Redis\SortedSetEntry.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.IO.Compression" />
</ItemGroup>
<ItemGroup>
<Folder Include="StackExchange\Redis\" />
</ItemGroup>
</Project>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>ef84877f-59be-41be-9013-e765af0bb72e</ProjectGuid>
<RootNamespace>StackExchange.Redis</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
\ No newline at end of file
// 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
...@@ -9,9 +9,22 @@ namespace StackExchange.Redis ...@@ -9,9 +9,22 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public sealed class CommandMap public sealed class CommandMap
{ {
private static readonly CommandMap private readonly byte[][] map;
@default = CreateImpl(null, null),
twemproxy = CreateImpl(null, exclusions: new HashSet<RedisCommand> internal CommandMap(byte[][] map)
{
this.map = map;
}
/// <summary>
/// The default commands specified by redis
/// </summary>
public static CommandMap Default { get; } = CreateImpl(null, null);
/// <summary>
/// The commands available to <a href="twemproxy">https://github.com/twitter/twemproxy</a>
/// </summary>
/// <remarks>https://github.com/twitter/twemproxy/blob/master/notes/redis.md</remarks>
public static CommandMap Twemproxy { get; } = CreateImpl(null, exclusions: new HashSet<RedisCommand>
{ {
// see https://github.com/twitter/twemproxy/blob/master/notes/redis.md // see https://github.com/twitter/twemproxy/blob/master/notes/redis.md
RedisCommand.KEYS, RedisCommand.MIGRATE, RedisCommand.MOVE, RedisCommand.OBJECT, RedisCommand.RANDOMKEY, RedisCommand.KEYS, RedisCommand.MIGRATE, RedisCommand.MOVE, RedisCommand.OBJECT, RedisCommand.RANDOMKEY,
...@@ -33,50 +46,33 @@ private static readonly CommandMap ...@@ -33,50 +46,33 @@ private static readonly CommandMap
RedisCommand.SCRIPT, RedisCommand.SCRIPT,
RedisCommand.AUTH, RedisCommand.ECHO, RedisCommand.PING, RedisCommand.QUIT, RedisCommand.SELECT, RedisCommand.ECHO, RedisCommand.PING, RedisCommand.QUIT, RedisCommand.SELECT,
RedisCommand.BGREWRITEAOF, RedisCommand.BGSAVE, RedisCommand.CLIENT, RedisCommand.CLUSTER, RedisCommand.CONFIG, RedisCommand.DBSIZE, 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.DEBUG, RedisCommand.FLUSHALL, RedisCommand.FLUSHDB, RedisCommand.INFO, RedisCommand.LASTSAVE, RedisCommand.MONITOR, RedisCommand.SAVE,
RedisCommand.SHUTDOWN, RedisCommand.SLAVEOF, RedisCommand.SLOWLOG, RedisCommand.SYNC, RedisCommand.TIME RedisCommand.SHUTDOWN, RedisCommand.SLAVEOF, RedisCommand.SLOWLOG, RedisCommand.SYNC, RedisCommand.TIME
}), ssdb = Create(new HashSet<string> { });
/// <summary>
/// The commands available to <a href="ssdb">http://www.ideawu.com/ssdb/</a>
/// </summary>
/// <remarks>http://www.ideawu.com/ssdb/docs/redis-to-ssdb.html</remarks>
public static CommandMap SSDB { get; } = Create(new HashSet<string> {
// see http://www.ideawu.com/ssdb/docs/redis-to-ssdb.html // see http://www.ideawu.com/ssdb/docs/redis-to-ssdb.html
"ping", "ping",
"get", "set", "del", "incr", "incrby", "mget", "mset", "keys", "getset", "setnx", "get", "set", "del", "incr", "incrby", "mget", "mset", "keys", "getset", "setnx",
"hget", "hset", "hdel", "hincrby", "hkeys", "hvals", "hmget", "hmset", "hlen", "hget", "hset", "hdel", "hincrby", "hkeys", "hvals", "hmget", "hmset", "hlen",
"zscore", "zadd", "zrem", "zrange", "zrangebyscore", "zincrby", "zdecrby", "zcard", "zscore", "zadd", "zrem", "zrange", "zrangebyscore", "zincrby", "zdecrby", "zcard",
"llen", "lpush", "rpush", "lpop", "rpop", "lrange", "lindex" "llen", "lpush", "rpush", "lpop", "rpop", "lrange", "lindex"
}, true), }, true);
sentinel = Create(new HashSet<string> {
// see http://redis.io/topics/sentinel
"ping", "info", "sentinel", "subscribe", "psubscribe", "unsubscribe", "punsubscribe" }, true);
private readonly byte[][] map;
internal CommandMap(byte[][] map)
{
this.map = map;
}
/// <summary>
/// The default commands specified by redis
/// </summary>
public static CommandMap Default { get { return @default; } }
/// <summary>
/// The commands available to <a href="twemproxy">https://github.com/twitter/twemproxy</a>
/// </summary>
/// <remarks>https://github.com/twitter/twemproxy/blob/master/notes/redis.md</remarks>
public static CommandMap Twemproxy { get { return twemproxy; } }
/// <summary>
/// The commands available to <a href="ssdb">http://www.ideawu.com/ssdb/</a>
/// </summary>
/// <remarks>http://www.ideawu.com/ssdb/docs/redis-to-ssdb.html</remarks>
public static CommandMap SSDB { get { return ssdb; } }
/// <summary> /// <summary>
/// The commands available to <a href="Sentinel">http://redis.io/topics/sentinel</a> /// The commands available to <a href="Sentinel">http://redis.io/topics/sentinel</a>
/// </summary> /// </summary>
/// <remarks>http://redis.io/topics/sentinel</remarks> /// <remarks>http://redis.io/topics/sentinel</remarks>
public static CommandMap Sentinel { get { return sentinel; } } public static CommandMap Sentinel { get; } = Create(new HashSet<string> {
// see http://redis.io/topics/sentinel
"ping", "info", "sentinel", "subscribe", "psubscribe", "unsubscribe", "punsubscribe" }, true);
/// <summary> /// <summary>
/// Create a new CommandMap, customizing some commands /// Create a new CommandMap, customizing some commands
...@@ -85,8 +81,7 @@ public static CommandMap Create(Dictionary<string, string> overrides) ...@@ -85,8 +81,7 @@ public static CommandMap Create(Dictionary<string, string> overrides)
{ {
if (overrides == null || overrides.Count == 0) return Default; if (overrides == null || overrides.Count == 0) return Default;
if (ReferenceEquals(overrides.Comparer, StringComparer.OrdinalIgnoreCase) || if (ReferenceEquals(overrides.Comparer, StringComparer.OrdinalIgnoreCase))
ReferenceEquals(overrides.Comparer, StringComparer.InvariantCultureIgnoreCase))
{ {
// that's ok; we're happy with ordinal/invariant case-insensitive // that's ok; we're happy with ordinal/invariant case-insensitive
// (but not culture-specific insensitive; completely untested) // (but not culture-specific insensitive; completely untested)
...@@ -209,13 +204,13 @@ private static CommandMap CreateImpl(Dictionary<string, string> caseInsensitiveO ...@@ -209,13 +204,13 @@ private static CommandMap CreateImpl(Dictionary<string, string> caseInsensitiveO
} }
} }
if (value != name) haveDelta = true; if (value != name) haveDelta = true;
// TODO: bug?
haveDelta = true; haveDelta = true;
byte[] val = string.IsNullOrWhiteSpace(value) ? null : Encoding.UTF8.GetBytes(value); byte[] val = string.IsNullOrWhiteSpace(value) ? null : Encoding.UTF8.GetBytes(value);
map[idx] = val; map[idx] = val;
} }
} }
if (!haveDelta && @default != null) return @default; if (!haveDelta && Default != null) return Default;
return new CommandMap(map); return new CommandMap(map);
} }
......
...@@ -11,13 +11,13 @@ public sealed class CommandTrace ...@@ -11,13 +11,13 @@ public sealed class CommandTrace
internal CommandTrace(long uniqueId, long time, long duration, RedisValue[] arguments) internal CommandTrace(long uniqueId, long time, long duration, RedisValue[] arguments)
{ {
this.UniqueId = uniqueId; UniqueId = uniqueId;
this.Time = RedisBase.UnixEpoch.AddSeconds(time); Time = RedisBase.UnixEpoch.AddSeconds(time);
// duration = The amount of time needed for its execution, in microseconds. // 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. // A tick is equal to 100 nanoseconds, or one ten-millionth of a second.
// So 1 microsecond = 10 ticks // So 1 microsecond = 10 ticks
this.Duration = TimeSpan.FromTicks(duration * 10); Duration = TimeSpan.FromTicks(duration * 10);
this.Arguments = arguments; Arguments = arguments;
} }
/// <summary> /// <summary>
......
using System;
namespace StackExchange.Redis
{
/// <summary>
/// Helper for Array.ConvertAll() as it's missing on .Net Core.
/// </summary>
public static class ConvertHelper
{
/// <summary>
/// Converts array of one type to an array of another type.
/// </summary>
/// <typeparam name="TInput">Input type</typeparam>
/// <typeparam name="TOutput">Output type</typeparam>
/// <param name="source">source</param>
/// <param name="selector">selector</param>
/// <returns></returns>
public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] source, Func<TInput, TOutput> 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
}
}
}
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
}
}
}
...@@ -4,7 +4,7 @@ namespace StackExchange.Redis ...@@ -4,7 +4,7 @@ namespace StackExchange.Redis
{ {
internal static class CompletedTask<T> internal static class CompletedTask<T>
{ {
private readonly static Task<T> @default = FromResult(default(T), null); private static readonly Task<T> @default = FromResult(default(T), null);
public static Task<T> Default(object asyncState) public static Task<T> Default(object asyncState)
{ {
......
...@@ -145,7 +145,7 @@ private void ProcessAsyncCompletionQueueImpl() ...@@ -145,7 +145,7 @@ private void ProcessAsyncCompletionQueueImpl()
// give it a moment and try again, noting that we might lose the battle // give it a moment and try again, noting that we might lose the battle
// when we pause // when we pause
Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread); Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread);
if (Thread.Yield() && Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) == 0) if (SpinWait() && Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) == 0)
{ {
// we paused, and we got the lock back; anything else? // we paused, and we got the lock back; anything else?
lock (asyncCompletionQueue) lock (asyncCompletionQueue)
...@@ -176,5 +176,20 @@ private void ProcessAsyncCompletionQueueImpl() ...@@ -176,5 +176,20 @@ private void ProcessAsyncCompletionQueueImpl()
Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread); 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;
}
} }
} }
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -31,8 +27,8 @@ public struct Enumerator : IEnumerator<IProfiledCommand> ...@@ -31,8 +27,8 @@ public struct Enumerator : IEnumerator<IProfiledCommand>
ProfileStorage Head; ProfileStorage Head;
ProfileStorage CurrentBacker; ProfileStorage CurrentBacker;
bool IsEmpty { get { return Head == null; } } bool IsEmpty => Head == null;
bool IsUnstartedOrFinished { get { return CurrentBacker == null; } } bool IsUnstartedOrFinished => CurrentBacker == null;
internal Enumerator(ProfileStorage head) internal Enumerator(ProfileStorage head)
{ {
...@@ -43,15 +39,9 @@ internal Enumerator(ProfileStorage head) ...@@ -43,15 +39,9 @@ internal Enumerator(ProfileStorage head)
/// <summary> /// <summary>
/// The current element. /// The current element.
/// </summary> /// </summary>
public IProfiledCommand Current public IProfiledCommand Current => CurrentBacker;
{
get { return CurrentBacker; }
}
object System.Collections.IEnumerator.Current object System.Collections.IEnumerator.Current => CurrentBacker;
{
get { return CurrentBacker; }
}
/// <summary> /// <summary>
/// Advances the enumeration, returning true if there is a new element to consume and false /// Advances the enumeration, returning true if there is a new element to consume and false
......
...@@ -7,11 +7,9 @@ namespace StackExchange.Redis ...@@ -7,11 +7,9 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public class ConnectionCounters public class ConnectionCounters
{ {
private readonly ConnectionType connectionType;
internal ConnectionCounters(ConnectionType connectionType) internal ConnectionCounters(ConnectionType connectionType)
{ {
this.connectionType = connectionType; ConnectionType = connectionType;
} }
/// <summary> /// <summary>
...@@ -27,7 +25,8 @@ internal ConnectionCounters(ConnectionType connectionType) ...@@ -27,7 +25,8 @@ internal ConnectionCounters(ConnectionType connectionType)
/// <summary> /// <summary>
/// The type of this connection /// The type of this connection
/// </summary> /// </summary>
public ConnectionType ConnectionType { get { return connectionType; } } public ConnectionType ConnectionType { get; }
/// <summary> /// <summary>
/// The number of operations that failed to complete asynchronously /// The number of operations that failed to complete asynchronously
/// </summary> /// </summary>
...@@ -36,10 +35,7 @@ internal ConnectionCounters(ConnectionType connectionType) ...@@ -36,10 +35,7 @@ internal ConnectionCounters(ConnectionType connectionType)
/// <summary> /// <summary>
/// Indicates if there are any pending items or failures on this connection /// Indicates if there are any pending items or failures on this connection
/// </summary> /// </summary>
public bool IsEmpty public bool IsEmpty => PendingUnsentItems == 0 && SentItemsAwaitingResponse == 0 && ResponsesAwaitingAsyncCompletion == 0 && FailedAsynchronously == 0;
{
get { return PendingUnsentItems == 0 && SentItemsAwaitingResponse == 0 && ResponsesAwaitingAsyncCompletion == 0 && FailedAsynchronously == 0; }
}
/// <summary> /// <summary>
/// Indicates the total number of messages despatched to a non-preferred endpoint, for example sent to a master /// Indicates the total number of messages despatched to a non-preferred endpoint, for example sent to a master
...@@ -80,7 +76,7 @@ public bool IsEmpty ...@@ -80,7 +76,7 @@ public bool IsEmpty
/// <summary> /// <summary>
/// Indicates the total number of outstanding items against this connection /// Indicates the total number of outstanding items against this connection
/// </summary> /// </summary>
public int TotalOutstanding { get { return PendingUnsentItems + SentItemsAwaitingResponse + ResponsesAwaitingAsyncCompletion; } } public int TotalOutstanding => PendingUnsentItems + SentItemsAwaitingResponse + ResponsesAwaitingAsyncCompletion;
/// <summary> /// <summary>
/// Indicates the total number of writers items against this connection /// Indicates the total number of writers items against this connection
...@@ -100,17 +96,17 @@ public override string ToString() ...@@ -100,17 +96,17 @@ public override string ToString()
internal void Add(ConnectionCounters other) internal void Add(ConnectionCounters other)
{ {
if (other == null) return; if (other == null) return;
this.CompletedAsynchronously += other.CompletedAsynchronously; CompletedAsynchronously += other.CompletedAsynchronously;
this.CompletedSynchronously += other.CompletedSynchronously; CompletedSynchronously += other.CompletedSynchronously;
this.FailedAsynchronously += other.FailedAsynchronously; FailedAsynchronously += other.FailedAsynchronously;
this.OperationCount += other.OperationCount; OperationCount += other.OperationCount;
this.PendingUnsentItems += other.PendingUnsentItems; PendingUnsentItems += other.PendingUnsentItems;
this.ResponsesAwaitingAsyncCompletion += other.ResponsesAwaitingAsyncCompletion; ResponsesAwaitingAsyncCompletion += other.ResponsesAwaitingAsyncCompletion;
this.SentItemsAwaitingResponse += other.SentItemsAwaitingResponse; SentItemsAwaitingResponse += other.SentItemsAwaitingResponse;
this.SocketCount += other.SocketCount; SocketCount += other.SocketCount;
this.Subscriptions += other.Subscriptions; Subscriptions += other.Subscriptions;
this.WriterCount += other.WriterCount; WriterCount += other.WriterCount;
this.NonPreferredEndpointCount += other.NonPreferredEndpointCount; NonPreferredEndpointCount += other.NonPreferredEndpointCount;
} }
internal bool Any() internal bool Any()
...@@ -135,5 +131,4 @@ internal void Append(StringBuilder sb) ...@@ -135,5 +131,4 @@ internal void Append(StringBuilder sb)
if (NonPreferredEndpointCount != 0) sb.Append(", non-pref=").Append(NonPreferredEndpointCount); if (NonPreferredEndpointCount != 0) sb.Append(", non-pref=").Append(NonPreferredEndpointCount);
} }
} }
} }
...@@ -9,57 +9,43 @@ namespace StackExchange.Redis ...@@ -9,57 +9,43 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public sealed class ConnectionFailedEventArgs : EventArgs, ICompletable public sealed class ConnectionFailedEventArgs : EventArgs, ICompletable
{ {
private readonly ConnectionType connectionType;
private readonly EndPoint endpoint;
private readonly Exception exception;
private readonly ConnectionFailureType failureType;
private readonly EventHandler<ConnectionFailedEventArgs> handler; private readonly EventHandler<ConnectionFailedEventArgs> handler;
private readonly object sender; private readonly object sender;
internal ConnectionFailedEventArgs(EventHandler<ConnectionFailedEventArgs> handler, object sender, EndPoint endPoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception) internal ConnectionFailedEventArgs(EventHandler<ConnectionFailedEventArgs> handler, object sender, EndPoint endPoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception)
{ {
this.handler = handler; this.handler = handler;
this.sender = sender; this.sender = sender;
this.endpoint = endPoint; EndPoint = endPoint;
this.connectionType = connectionType; ConnectionType = connectionType;
this.exception = exception; Exception = exception;
this.failureType = failureType; FailureType = failureType;
} }
/// <summary> /// <summary>
/// Gets the connection-type of the failing connection /// Gets the connection-type of the failing connection
/// </summary> /// </summary>
public ConnectionType ConnectionType public ConnectionType ConnectionType { get; }
{
get { return connectionType; }
}
/// <summary> /// <summary>
/// Gets the failing server-endpoint /// Gets the failing server-endpoint
/// </summary> /// </summary>
public EndPoint EndPoint public EndPoint EndPoint { get; }
{
get { return endpoint; }
}
/// <summary> /// <summary>
/// Gets the exception if available (this can be null) /// Gets the exception if available (this can be null)
/// </summary> /// </summary>
public Exception Exception public Exception Exception { get; }
{
get { return exception; }
}
/// <summary> /// <summary>
/// The type of failure /// The type of failure
/// </summary> /// </summary>
public ConnectionFailureType FailureType public ConnectionFailureType FailureType { get; }
{
get { return failureType; }
}
void ICompletable.AppendStormLog(StringBuilder sb) void ICompletable.AppendStormLog(StringBuilder sb)
{ {
sb.Append("event, connection-failed: "); sb.Append("event, connection-failed: ");
if (endpoint == null) sb.Append("n/a"); if (EndPoint == null) sb.Append("n/a");
else sb.Append(Format.ToString(endpoint)); else sb.Append(Format.ToString(EndPoint));
} }
bool ICompletable.TryComplete(bool isAsync) bool ICompletable.TryComplete(bool isAsync)
......
...@@ -18,7 +18,7 @@ public enum ConnectionFailureType ...@@ -18,7 +18,7 @@ public enum ConnectionFailureType
/// </summary> /// </summary>
SocketFailure, SocketFailure,
/// <summary> /// <summary>
/// The connection did not authenticate correctly /// Either SSL Stream or Redis authentication failed
/// </summary> /// </summary>
AuthenticationFailure, AuthenticationFailure,
/// <summary> /// <summary>
......
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -24,7 +18,7 @@ partial class ConnectionMultiplexer ...@@ -24,7 +18,7 @@ partial class ConnectionMultiplexer
/// </summary> /// </summary>
public void RegisterProfiler(IProfiler profiler) public void RegisterProfiler(IProfiler profiler)
{ {
if (profiler == null) throw new ArgumentNullException("profiler"); if (profiler == null) throw new ArgumentNullException(nameof(profiler));
if (this.profiler != null) throw new InvalidOperationException("IProfiler already registered for this ConnectionMultiplexer"); if (this.profiler != null) throw new InvalidOperationException("IProfiler already registered for this ConnectionMultiplexer");
this.profiler = profiler; this.profiler = profiler;
...@@ -44,8 +38,8 @@ public void RegisterProfiler(IProfiler profiler) ...@@ -44,8 +38,8 @@ public void RegisterProfiler(IProfiler profiler)
public void BeginProfiling(object forContext) public void BeginProfiling(object forContext)
{ {
if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler"); if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler");
if (forContext == null) throw new ArgumentNullException("forContext"); if (forContext == null) throw new ArgumentNullException(nameof(forContext));
if (forContext is WeakReference) throw new ArgumentException("Context object cannot be a WeakReference", "forContext"); if (forContext is WeakReference) throw new ArgumentException("Context object cannot be a WeakReference", nameof(forContext));
if (!profiledCommands.TryCreate(forContext)) if (!profiledCommands.TryCreate(forContext))
{ {
...@@ -61,7 +55,7 @@ public void BeginProfiling(object forContext) ...@@ -61,7 +55,7 @@ public void BeginProfiling(object forContext)
public ProfiledCommandEnumerable FinishProfiling(object forContext, bool allowCleanupSweep = true) 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 (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler");
if (forContext == null) throw new ArgumentNullException("forContext"); if (forContext == null) throw new ArgumentNullException(nameof(forContext));
ProfiledCommandEnumerable ret; ProfiledCommandEnumerable ret;
if (!profiledCommands.TryRemove(forContext, out ret)) if (!profiledCommands.TryRemove(forContext, out ret))
......
...@@ -120,7 +120,7 @@ public static string[] ToStringArray(this RedisValue[] values) ...@@ -120,7 +120,7 @@ public static string[] ToStringArray(this RedisValue[] values)
{ {
if (values == null) return null; if (values == null) return null;
if (values.Length == 0) return nix; if (values.Length == 0) return nix;
return Array.ConvertAll(values, x => (string)x); return ConvertHelper.ConvertAll(values, x => (string)x);
} }
} }
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment