Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
StackExchange.Redis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
tsai
StackExchange.Redis
Commits
217efc0f
Commit
217efc0f
authored
Jun 11, 2019
by
mgravell
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
spike to see what we'd need to do more efficient eval/execute - does not even remotely work
parent
41c6175e
Changes
13
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
476 additions
and
134 deletions
+476
-134
CommandFlags.cs
src/StackExchange.Redis/Enums/CommandFlags.cs
+1
-0
IDatabase.cs
src/StackExchange.Redis/Interfaces/IDatabase.cs
+35
-0
IDatabaseAsync.cs
src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
+35
-0
IServer.cs
src/StackExchange.Redis/Interfaces/IServer.cs
+24
-0
DatabaseWrapper.cs
src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
+31
-4
WrapperBase.cs
src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
+149
-24
LuaScript.cs
src/StackExchange.Redis/LuaScript.cs
+10
-10
Message.cs
src/StackExchange.Redis/Message.cs
+11
-1
RedisDatabase.cs
src/StackExchange.Redis/RedisDatabase.cs
+72
-28
RedisServer.cs
src/StackExchange.Redis/RedisServer.cs
+12
-3
ScriptParameterMapper.cs
src/StackExchange.Redis/ScriptParameterMapper.cs
+84
-49
Cluster.cs
tests/StackExchange.Redis.Tests/Cluster.cs
+1
-1
Scripting.cs
tests/StackExchange.Redis.Tests/Scripting.cs
+11
-14
No files found.
src/StackExchange.Redis/Enums/CommandFlags.cs
View file @
217efc0f
...
...
@@ -65,5 +65,6 @@ public enum CommandFlags
NoScriptCache
=
512
,
// 1024: used for timed-out; never user-specified, so not visible on the public API
// 2048: used to indicate to auto-recycle arrays; we may add this to the public API later
}
}
src/StackExchange.Redis/Interfaces/IDatabase.cs
View file @
217efc0f
...
...
@@ -822,6 +822,18 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <returns>A dynamic representation of the command's result</returns>
RedisResult
Execute
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for
/// executing modules, but may also be used to provide access to new features that lack
/// a direct API.
/// </summary>
/// <param name="command">The command to run.</param>
/// <param name="args">The arguments to pass for the command.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <remarks>This API should be considered an advanced feature; inappropriate use can be harmful</remarks>
/// <returns>A dynamic representation of the command's result</returns>
RedisResult
Execute
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server.
/// </summary>
...
...
@@ -834,6 +846,18 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>https://redis.io/commands/evalsha</remarks>
RedisResult
ScriptEvaluate
(
string
script
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server.
/// </summary>
/// <param name="script">The script to execute.</param>
/// <param name="keys">The keys to execute against.</param>
/// <param name="values">The values to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's result</returns>
/// <remarks>https://redis.io/commands/eval</remarks>
/// <remarks>https://redis.io/commands/evalsha</remarks>
RedisResult
ScriptEvaluate
(
string
script
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server using just the SHA1 hash
/// </summary>
...
...
@@ -845,6 +869,17 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>https://redis.io/commands/evalsha</remarks>
RedisResult
ScriptEvaluate
(
byte
[]
hash
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server using just the SHA1 hash
/// </summary>
/// <param name="hash">The hash of the script to execute.</param>
/// <param name="keys">The keys to execute against.</param>
/// <param name="values">The values to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's result</returns>
/// <remarks>https://redis.io/commands/evalsha</remarks>
RedisResult
ScriptEvaluate
(
byte
[]
hash
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a lua script against the server, using previously prepared script.
/// Named parameters, if any, are provided by the `parameters` object.
...
...
src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
View file @
217efc0f
...
...
@@ -786,6 +786,18 @@ public interface IDatabaseAsync : IRedisAsync
/// <returns>A dynamic representation of the command's result</returns>
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for
/// executing modules, but may also be used to provide access to new features that lack
/// a direct API.
/// </summary>
/// <param name="command">The command to run.</param>
/// <param name="args">The arguments to pass for the command.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <remarks>This API should be considered an advanced feature; inappropriate use can be harmful</remarks>
/// <returns>A dynamic representation of the command's result</returns>
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server.
/// </summary>
...
...
@@ -798,6 +810,18 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>https://redis.io/commands/evalsha</remarks>
Task
<
RedisResult
>
ScriptEvaluateAsync
(
string
script
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server.
/// </summary>
/// <param name="script">The script to execute.</param>
/// <param name="keys">The keys to execute against.</param>
/// <param name="values">The values to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's result</returns>
/// <remarks>https://redis.io/commands/eval</remarks>
/// <remarks>https://redis.io/commands/evalsha</remarks>
Task
<
RedisResult
>
ScriptEvaluateAsync
(
string
script
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server using just the SHA1 hash
/// </summary>
...
...
@@ -809,6 +833,17 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>https://redis.io/commands/evalsha</remarks>
Task
<
RedisResult
>
ScriptEvaluateAsync
(
byte
[]
hash
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a Lua script against the server using just the SHA1 hash
/// </summary>
/// <param name="hash">The hash of the script to execute.</param>
/// <param name="keys">The keys to execute against.</param>
/// <param name="values">The values to execute against.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>A dynamic representation of the script's result</returns>
/// <remarks>https://redis.io/commands/evalsha</remarks>
Task
<
RedisResult
>
ScriptEvaluateAsync
(
byte
[]
hash
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute a lua script against the server, using previously prepared script.
/// Named parameters, if any, are provided by the `parameters` object.
...
...
src/StackExchange.Redis/Interfaces/IServer.cs
View file @
217efc0f
...
...
@@ -258,6 +258,18 @@ public partial interface IServer : IRedis
/// <returns>A dynamic representation of the command's result</returns>
RedisResult
Execute
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for
/// executing modules, but may also be used to provide access to new features that lack
/// a direct API.
/// </summary>
/// <param name="command">The command to run.</param>
/// <param name="args">The arguments to pass for the command.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <remarks>This API should be considered an advanced feature; inappropriate use can be harmful</remarks>
/// <returns>A dynamic representation of the command's result</returns>
RedisResult
Execute
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for
/// executing modules, but may also be used to provide access to new features that lack
...
...
@@ -281,6 +293,18 @@ public partial interface IServer : IRedis
/// <returns>A dynamic representation of the command's result</returns>
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Execute an arbitrary command against the server; this is primarily intended for
/// executing modules, but may also be used to provide access to new features that lack
/// a direct API.
/// </summary>
/// <param name="command">The command to run.</param>
/// <param name="args">The arguments to pass for the command.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <remarks>This API should be considered an advanced feature; inappropriate use can be harmful</remarks>
/// <returns>A dynamic representation of the command's result</returns>
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// Delete all the keys of all databases on the server.
/// </summary>
...
...
src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
View file @
217efc0f
...
...
@@ -377,21 +377,48 @@ public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags
}
public
RedisResult
Execute
(
string
command
,
params
object
[]
args
)
=>
Inner
.
Execute
(
command
,
ToInner
(
args
),
CommandFlags
.
None
);
=>
Inner
.
Execute
(
command
,
LeaseInner
(
args
),
CommandFlags
.
None
|
Message
.
AutoRecycleMemories
);
public
RedisResult
Execute
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
=>
Inner
.
Execute
(
command
,
ToInner
(
args
),
flags
);
=>
Inner
.
Execute
(
command
,
LeaseInnerCol
(
args
),
flags
|
Message
.
AutoRecycleMemories
);
public
RedisResult
Execute
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
=>
Inner
.
Execute
(
command
,
LeaseInner
(
args
.
Span
),
flags
|
Message
.
AutoRecycleMemories
);
public
RedisResult
ScriptEvaluate
(
byte
[]
hash
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return
Inner
.
ScriptEvaluate
(
hash
,
ToInner
(
keys
),
values
,
flags
);
using
(
var
inner
=
LeaseInner
(
keys
))
{
return
Inner
.
ScriptEvaluate
(
hash
,
inner
,
values
,
flags
);
}
}
public
RedisResult
ScriptEvaluate
(
string
script
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return
Inner
.
ScriptEvaluate
(
script
,
ToInner
(
keys
),
values
,
flags
);
using
(
var
inner
=
LeaseInner
(
keys
))
{
return
Inner
.
ScriptEvaluate
(
script
,
inner
,
values
,
flags
);
}
}
public
RedisResult
ScriptEvaluate
(
string
script
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
using
(
var
inner
=
LeaseInner
(
keys
.
Span
))
{
return
Inner
.
ScriptEvaluate
(
script
,
inner
,
values
,
flags
);
}
}
public
RedisResult
ScriptEvaluate
(
byte
[]
hash
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
using
(
var
inner
=
LeaseInner
(
keys
.
Span
))
{
return
Inner
.
ScriptEvaluate
(
hash
,
inner
,
values
,
flags
);
}
}
public
RedisResult
ScriptEvaluate
(
LuaScript
script
,
object
parameters
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
...
...
src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
View file @
217efc0f
using
System
;
using
System.Buffers
;
using
System.Collections.Generic
;
using
System.Diagnostics
;
using
System.Linq
;
using
System.Net
;
using
System.Runtime.CompilerServices
;
using
System.Threading.Tasks
;
namespace
StackExchange.Redis.KeyspaceIsolation
...
...
@@ -359,21 +362,48 @@ public Task<long> PublishAsync(RedisChannel channel, RedisValue message, Command
}
public
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
params
object
[]
args
)
=>
Inner
.
ExecuteAsync
(
command
,
ToInner
(
args
),
CommandFlags
.
None
);
=>
Inner
.
ExecuteAsync
(
command
,
LeaseInner
(
args
),
CommandFlags
.
None
|
Message
.
AutoRecycleMemories
);
public
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
=>
Inner
.
ExecuteAsync
(
command
,
ToInner
(
args
),
flag
s
);
=>
Inner
.
ExecuteAsync
(
command
,
LeaseInnerCol
(
args
),
flags
|
Message
.
AutoRecycleMemorie
s
);
public
Task
<
RedisResult
>
ScriptEvaluateAsync
(
byte
[]
hash
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
public
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
=>
Inner
.
ExecuteAsync
(
command
,
LeaseInner
(
args
.
Span
),
flags
|
Message
.
AutoRecycleMemories
);
public
async
Task
<
RedisResult
>
ScriptEvaluateAsync
(
byte
[]
hash
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return
Inner
.
ScriptEvaluateAsync
(
hash
,
ToInner
(
keys
),
values
,
flags
);
using
(
var
inner
=
LeaseInner
(
keys
))
{
return
await
Inner
.
ScriptEvaluateAsync
(
hash
,
inner
,
values
,
flags
);
}
}
public
async
Task
<
RedisResult
>
ScriptEvaluateAsync
(
string
script
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
using
(
var
inner
=
LeaseInner
(
keys
))
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return
await
Inner
.
ScriptEvaluateAsync
(
script
,
inner
,
values
,
flags
);
}
}
public
Task
<
RedisResult
>
ScriptEvaluateAsync
(
string
script
,
RedisKey
[]
keys
=
null
,
RedisValue
[]
values
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
public
async
Task
<
RedisResult
>
ScriptEvaluateAsync
(
byte
[]
hash
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return
Inner
.
ScriptEvaluateAsync
(
script
,
ToInner
(
keys
),
values
,
flags
);
using
(
var
inner
=
LeaseInner
(
keys
.
Span
))
{
return
await
Inner
.
ScriptEvaluateAsync
(
hash
,
inner
,
values
,
flags
);
}
}
public
async
Task
<
RedisResult
>
ScriptEvaluateAsync
(
string
script
,
ReadOnlyMemory
<
RedisKey
>
keys
,
ReadOnlyMemory
<
RedisValue
>
values
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
using
(
var
inner
=
LeaseInner
(
keys
.
Span
))
{
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return
await
Inner
.
ScriptEvaluateAsync
(
script
,
inner
,
values
,
flags
);
}
}
public
Task
<
RedisResult
>
ScriptEvaluateAsync
(
LuaScript
script
,
object
parameters
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
...
...
@@ -862,13 +892,90 @@ protected RedisKey ToInnerOrDefault(RedisKey outer)
}
}
internal
readonly
struct
ValueLease
<
T
>
:
IDisposable
{
private
readonly
T
[]
_array
;
private
readonly
int
_length
;
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
internal
ValueLease
(
T
[]
array
,
int
length
)
{
_array
=
array
;
_length
=
length
;
Debug
.
Assert
(
array
!=
null
);
Debug
.
Assert
(
length
>=
array
.
Length
);
}
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
internal
static
ValueLease
<
T
>
Rent
(
int
size
)
=>
new
ValueLease
<
T
>(
ArrayPool
<
T
>.
Shared
.
Rent
(
size
),
size
);
public
Memory
<
T
>
Memory
{
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
get
=>
new
Memory
<
T
>(
_array
,
0
,
_length
);
}
public
Span
<
T
>
Span
{
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
get
=>
new
Span
<
T
>(
_array
,
0
,
_length
);
}
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
public
static
implicit
operator
Memory
<
T
>
(
ValueLease
<
T
>
lease
)
=>
lease
.
Memory
;
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
public
static
implicit
operator
ReadOnlyMemory
<
T
>(
ValueLease
<
T
>
lease
)
=>
lease
.
Memory
;
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
public
static
implicit
operator
Span
<
T
>(
ValueLease
<
T
>
lease
)
=>
lease
.
Span
;
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
public
static
implicit
operator
ReadOnlySpan
<
T
>(
ValueLease
<
T
>
lease
)
=>
lease
.
Span
;
[
MethodImpl
(
MethodImplOptions
.
AggressiveInlining
)]
public
void
Dispose
()
{
if
(
_length
!=
0
)
ArrayPool
<
T
>.
Shared
.
Return
(
_array
);
}
}
protected
ValueLease
<
object
>
LeaseInnerCol
(
ICollection
<
object
>
args
)
{
if
(
args
==
null
||
args
.
Count
==
0
)
return
default
;
var
withPrefix
=
ArrayPool
<
object
>.
Shared
.
Rent
(
args
.
Count
);
AddPrefix
(
args
,
withPrefix
);
return
new
ValueLease
<
object
>(
withPrefix
,
args
.
Count
);
}
protected
ValueLease
<
object
>
LeaseInner
(
ReadOnlySpan
<
object
>
args
)
{
if
(
args
.
IsEmpty
)
return
default
;
var
withPrefix
=
ArrayPool
<
object
>.
Shared
.
Rent
(
args
.
Length
);
AddPrefix
(
args
,
withPrefix
);
return
new
ValueLease
<
object
>(
withPrefix
,
args
.
Length
);
}
protected
ValueLease
<
RedisKey
>
LeaseInner
(
ReadOnlySpan
<
RedisKey
>
args
)
{
if
(
args
.
IsEmpty
)
return
default
;
var
withPrefix
=
ArrayPool
<
RedisKey
>.
Shared
.
Rent
(
args
.
Length
);
for
(
int
i
=
0
;
i
<
args
.
Length
;
i
++)
{
withPrefix
[
i
]
=
ToInner
(
args
[
i
]);
}
return
new
ValueLease
<
RedisKey
>(
withPrefix
,
args
.
Length
);
}
protected
ICollection
<
object
>
ToInner
(
ICollection
<
object
>
args
)
{
if
(
args
?.
Any
(
x
=>
x
is
RedisKey
||
x
is
RedisChannel
)
==
true
)
{
var
withPrefix
=
new
object
[
args
.
Count
];
AddPrefix
(
args
,
withPrefix
);
args
=
withPrefix
;
}
return
args
;
}
private
void
AddPrefix
(
ReadOnlySpan
<
object
>
from
,
object
[]
to
)
{
int
i
=
0
;
foreach
(
var
oldArg
in
args
)
foreach
(
var
oldArg
in
from
)
{
object
newArg
;
if
(
oldArg
is
RedisKey
key
)
...
...
@@ -883,11 +990,29 @@ protected ICollection<object> ToInner(ICollection<object> args)
{
newArg
=
oldArg
;
}
withPrefix
[
i
++]
=
newArg
;
to
[
i
++]
=
newArg
;
}
args
=
withPrefix
;
}
return
args
;
private
void
AddPrefix
(
ICollection
<
object
>
from
,
object
[]
to
)
{
int
i
=
0
;
foreach
(
var
oldArg
in
from
)
{
object
newArg
;
if
(
oldArg
is
RedisKey
key
)
{
newArg
=
ToInner
(
key
);
}
else
if
(
oldArg
is
RedisChannel
channel
)
{
newArg
=
ToInner
(
channel
);
}
else
{
newArg
=
oldArg
;
}
to
[
i
++]
=
newArg
;
}
}
protected
RedisKey
[]
ToInner
(
RedisKey
[]
outer
)
...
...
src/StackExchange.Redis/LuaScript.cs
View file @
217efc0f
...
...
@@ -95,7 +95,7 @@ public static LuaScript Prepare(string script)
return
ret
;
}
internal
void
ExtractParameters
(
object
ps
,
RedisKey
?
keyPrefix
,
out
Re
disKey
[]
keys
,
out
RedisValue
[]
args
)
internal
void
ExtractParameters
(
object
ps
,
RedisKey
?
keyPrefix
,
out
Re
adOnlyMemory
<
RedisKey
>
keys
,
out
ReadOnlyMemory
<
RedisValue
>
args
)
{
if
(
HasArguments
)
{
...
...
@@ -127,12 +127,12 @@ internal void ExtractParameters(object ps, RedisKey? keyPrefix, out RedisKey[] k
var
mapped
=
mapper
(
ps
,
keyPrefix
);
keys
=
mapped
.
Keys
;
args
=
mapped
.
Arg
ument
s
;
args
=
mapped
.
Args
;
}
else
{
keys
=
null
;
args
=
null
;
keys
=
default
;
args
=
default
;
}
}
...
...
@@ -145,8 +145,8 @@ internal void ExtractParameters(object ps, RedisKey? keyPrefix, out RedisKey[] k
/// <param name="flags">The command flags to use.</param>
public
RedisResult
Evaluate
(
IDatabase
db
,
object
ps
=
null
,
RedisKey
?
withKeyPrefix
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
ExtractParameters
(
ps
,
withKeyPrefix
,
out
RedisKey
[]
keys
,
out
RedisValue
[]
args
);
return
db
.
ScriptEvaluate
(
ExecutableScript
,
keys
,
args
,
flags
);
ExtractParameters
(
ps
,
withKeyPrefix
,
out
var
keys
,
out
var
args
);
return
db
.
ScriptEvaluate
(
ExecutableScript
,
keys
,
args
,
flags
|
Message
.
AutoRecycleMemories
);
}
/// <summary>
...
...
@@ -158,8 +158,8 @@ public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPre
/// <param name="flags">The command flags to use.</param>
public
Task
<
RedisResult
>
EvaluateAsync
(
IDatabaseAsync
db
,
object
ps
=
null
,
RedisKey
?
withKeyPrefix
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
ExtractParameters
(
ps
,
withKeyPrefix
,
out
RedisKey
[]
keys
,
out
RedisValue
[]
args
);
return
db
.
ScriptEvaluateAsync
(
ExecutableScript
,
keys
,
args
,
flags
);
ExtractParameters
(
ps
,
withKeyPrefix
,
out
var
keys
,
out
var
args
);
return
db
.
ScriptEvaluateAsync
(
ExecutableScript
,
keys
,
args
,
flags
|
Message
.
AutoRecycleMemories
);
}
/// <summary>
...
...
@@ -263,7 +263,7 @@ internal LoadedLuaScript(LuaScript original, byte[] hash)
/// <param name="flags">The command flags to use.</param>
public
RedisResult
Evaluate
(
IDatabase
db
,
object
ps
=
null
,
RedisKey
?
withKeyPrefix
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
Original
.
ExtractParameters
(
ps
,
withKeyPrefix
,
out
RedisKey
[]
keys
,
out
RedisValue
[]
args
);
Original
.
ExtractParameters
(
ps
,
withKeyPrefix
,
out
var
keys
,
out
var
args
);
return
db
.
ScriptEvaluate
(
Hash
,
keys
,
args
,
flags
);
}
...
...
@@ -280,7 +280,7 @@ public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPre
/// <param name="flags">The command flags to use.</param>
public
Task
<
RedisResult
>
EvaluateAsync
(
IDatabaseAsync
db
,
object
ps
=
null
,
RedisKey
?
withKeyPrefix
=
null
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
Original
.
ExtractParameters
(
ps
,
withKeyPrefix
,
out
RedisKey
[]
keys
,
out
RedisValue
[]
args
);
Original
.
ExtractParameters
(
ps
,
withKeyPrefix
,
out
var
keys
,
out
var
args
);
return
db
.
ScriptEvaluateAsync
(
Hash
,
keys
,
args
,
flags
);
}
}
...
...
src/StackExchange.Redis/Message.cs
View file @
217efc0f
using
System
;
using
System.Buffers
;
using
System.Collections.Generic
;
using
System.Diagnostics
;
using
System.IO
;
using
System.Linq
;
using
System.Runtime.CompilerServices
;
using
System.Runtime.InteropServices
;
using
System.Text
;
using
System.Threading
;
using
System.Threading.Tasks
;
...
...
@@ -52,6 +54,12 @@ protected override void WriteImpl(PhysicalConnection physical)
internal
abstract
class
Message
:
ICompletable
{
protected
static
void
Recycle
<
T
>(
in
ReadOnlyMemory
<
T
>
memory
)
{
if
(!
memory
.
IsEmpty
&&
MemoryMarshal
.
TryGetArray
(
memory
,
out
var
segment
))
ArrayPool
<
T
>.
Shared
.
Return
(
segment
.
Array
);
}
public
virtual
void
RecycleMemories
()
{
}
public
readonly
int
Db
;
#if DEBUG
...
...
@@ -67,7 +75,9 @@ internal void SetBacklogState(int position, PhysicalConnection physical)
#endif
}
internal
const
CommandFlags
InternalCallFlag
=
(
CommandFlags
)
128
;
internal
const
CommandFlags
InternalCallFlag
=
(
CommandFlags
)
128
,
AutoRecycleMemories
=
(
CommandFlags
)
2048
;
protected
RedisCommand
command
;
...
...
src/StackExchange.Redis/RedisDatabase.cs
View file @
217efc0f
This diff is collapsed.
Click to expand it.
src/StackExchange.Redis/RedisServer.cs
View file @
217efc0f
...
...
@@ -849,17 +849,26 @@ public Task SentinelFailoverAsync(string serviceName, CommandFlags flags = Comma
#
endregion
public
RedisResult
Execute
(
string
command
,
params
object
[]
args
)
=>
Execute
(
command
,
args
,
CommandFlags
.
None
);
public
RedisResult
Execute
(
string
command
,
params
object
[]
args
)
=>
Execute
(
command
,
(
ReadOnlyMemory
<
object
>)
args
,
CommandFlags
.
None
);
public
RedisResult
Execute
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
var
cpy
=
RedisDatabase
.
LeasedCopy
(
args
,
ref
flags
);
return
Execute
(
command
,
cpy
,
flags
);
}
public
RedisResult
Execute
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
var
msg
=
new
RedisDatabase
.
ExecuteMessage
(
multiplexer
?.
CommandMap
,
-
1
,
flags
,
command
,
args
);
return
ExecuteSync
(
msg
,
ResultProcessor
.
ScriptResult
);
}
public
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
params
object
[]
args
)
=>
ExecuteAsync
(
command
,
args
,
CommandFlags
.
None
);
public
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
params
object
[]
args
)
=>
ExecuteAsync
(
command
,
(
ReadOnlyMemory
<
object
>)
args
,
CommandFlags
.
None
);
public
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ICollection
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
var
cpy
=
RedisDatabase
.
LeasedCopy
(
args
,
ref
flags
);
return
ExecuteAsync
(
command
,
cpy
,
flags
);
}
public
Task
<
RedisResult
>
ExecuteAsync
(
string
command
,
ReadOnlyMemory
<
object
>
args
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
var
msg
=
new
RedisDatabase
.
ExecuteMessage
(
multiplexer
?.
CommandMap
,
-
1
,
flags
,
command
,
args
);
return
ExecuteAsync
(
msg
,
ResultProcessor
.
ScriptResult
);
...
...
src/StackExchange.Redis/ScriptParameterMapper.cs
View file @
217efc0f
using
System
;
using
System.Buffers
;
using
System.Collections.Generic
;
using
System.Diagnostics
;
using
System.Linq
;
using
System.Linq.Expressions
;
using
System.Reflection
;
...
...
@@ -12,15 +14,21 @@ internal static class ScriptParameterMapper
{
public
readonly
struct
ScriptParameters
{
public
readonly
RedisKey
[]
Keys
;
public
readonly
RedisValue
[]
Arguments
;
public
readonly
RedisKey
[]
KeyArray
;
public
readonly
RedisValue
[]
ArgArray
;
public
readonly
int
KeyCount
;
public
readonly
int
ArgCount
;
public
static
readonly
ConstructorInfo
Cons
=
typeof
(
ScriptParameters
).
GetConstructor
(
new
[]
{
typeof
(
RedisKey
[]),
typeof
(
RedisValue
[]
)
});
public
ScriptParameters
(
RedisKey
[]
keys
,
RedisValue
[]
args
)
public
static
readonly
ConstructorInfo
Cons
tructor
=
typeof
(
ScriptParameters
).
GetConstructor
(
new
[]
{
typeof
(
int
),
typeof
(
int
)
});
public
ScriptParameters
(
int
keyCount
,
int
argCount
)
{
Keys
=
keys
;
Arguments
=
args
;
KeyCount
=
keyCount
;
KeyArray
=
keyCount
==
0
?
Array
.
Empty
<
RedisKey
>()
:
ArrayPool
<
RedisKey
>.
Shared
.
Rent
(
keyCount
);
ArgCount
=
argCount
;
ArgArray
=
argCount
==
0
?
Array
.
Empty
<
RedisValue
>()
:
ArrayPool
<
RedisValue
>.
Shared
.
Rent
(
argCount
);
}
public
ReadOnlyMemory
<
RedisKey
>
Keys
=>
new
ReadOnlyMemory
<
RedisKey
>(
KeyArray
,
0
,
KeyCount
);
public
ReadOnlyMemory
<
RedisValue
>
Args
=>
new
ReadOnlyMemory
<
RedisValue
>(
ArgArray
,
0
,
ArgCount
);
}
private
static
readonly
Regex
ParameterExtractor
=
new
Regex
(
@"@(?<paramName> ([a-z]|_) ([a-z]|_|\d)*)"
,
RegexOptions
.
Compiled
|
RegexOptions
.
IgnoreCase
|
RegexOptions
.
IgnorePatternWhitespace
);
...
...
@@ -187,6 +195,9 @@ public static bool IsValidParameterHash(Type t, LuaScript script, out string mis
return
true
;
}
private
static
readonly
MethodInfo
s_prepend
=
typeof
(
RedisKey
).
GetMethod
(
nameof
(
RedisKey
.
Prepend
),
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
);
private
static
readonly
MethodInfo
s_asRedisValue
=
typeof
(
RedisKey
).
GetMethod
(
nameof
(
RedisKey
.
AsRedisValue
),
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
);
/// <summary>
/// <para>Creates a Func that extracts parameters from the given type for use by a LuaScript.</para>
/// <para>
...
...
@@ -241,65 +252,89 @@ Expression GetMember(Expression root, MemberInfo member)
args
.
Add
(
member
);
}
// parameters
var
objUntyped
=
Expression
.
Parameter
(
typeof
(
object
),
"obj"
);
var
objTyped
=
Expression
.
Convert
(
objUntyped
,
t
);
var
keyPrefix
=
Expression
.
Parameter
(
typeof
(
RedisKey
?),
"keyPrefix"
);
Expression
keysResult
,
valuesResult
;
MethodInfo
asRedisValue
=
null
;
Expression
[]
keysResultArr
=
null
;
if
(
keys
.
Count
==
0
)
{
// if there are no keys, don't allocate
keysResult
=
Expression
.
Constant
(
null
,
typeof
(
RedisKey
[]));
}
else
// locals
var
operations
=
new
List
<
Expression
>();
var
objTyped
=
Expression
.
Variable
(
t
);
var
result
=
Expression
.
Variable
(
typeof
(
ScriptParameters
));
var
keyArr
=
Expression
.
Variable
(
typeof
(
RedisKey
[]));
var
argArr
=
Expression
.
Variable
(
typeof
(
RedisValue
[]));
var
needsPrefix
=
Expression
.
Variable
(
typeof
(
bool
));
var
prefixValue
=
Expression
.
Variable
(
typeof
(
RedisKey
));
// objTyped = (t)objUntyped
operations
.
Add
(
Expression
.
Assign
(
objTyped
,
Expression
.
Convert
(
objUntyped
,
t
)));
// result = new ScriptParameters(keys.Count, args.Count)
operations
.
Add
(
Expression
.
Assign
(
result
,
Expression
.
New
(
ScriptParameters
.
Constructor
,
Expression
.
Constant
(
keys
.
Count
),
Expression
.
Constant
(
args
.
Count
))));
if
(
keys
.
Count
!=
0
)
{
// keyArr = result.KeyArray;
operations
.
Add
(
Expression
.
Assign
(
keyArr
,
Expression
.
PropertyOrField
(
result
,
nameof
(
ScriptParameters
.
KeyArray
))));
// needsPrefix = keyPrefix.HasValue
// prefixValue = prefixValue.GetValueOrDefault()
operations
.
Add
(
Expression
.
Assign
(
needsPrefix
,
Expression
.
PropertyOrField
(
keyPrefix
,
nameof
(
Nullable
<
RedisKey
>.
HasValue
))));
operations
.
Add
(
Expression
.
Assign
(
prefixValue
,
Expression
.
Call
(
keyPrefix
,
nameof
(
Nullable
<
RedisKey
>.
GetValueOrDefault
),
null
,
null
)));
var
needsKeyPrefix
=
Expression
.
Property
(
keyPrefix
,
nameof
(
Nullable
<
RedisKey
>.
HasValue
));
var
keyPrefixValueArr
=
new
[]
{
Expression
.
Call
(
keyPrefix
,
nameof
(
Nullable
<
RedisKey
>.
GetValueOrDefault
),
null
,
null
)
};
var
prepend
=
typeof
(
RedisKey
).
GetMethod
(
nameof
(
RedisKey
.
Prepend
),
BindingFlags
.
Public
|
BindingFlags
.
Instance
);
asRedisValue
=
typeof
(
RedisKey
).
GetMethod
(
nameof
(
RedisKey
.
AsRedisValue
),
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
);
var
prefixValueAsArgs
=
new
[]
{
prefixValue
};
keysResultArr
=
new
Expression
[
keys
.
Count
]
;
for
(
int
i
=
0
;
i
<
keysResultArr
.
Length
;
i
++
)
int
i
=
0
;
for
each
(
var
key
in
keys
)
{
var
member
=
GetMember
(
objTyped
,
keys
[
i
]);
keysResultArr
[
i
]
=
Expression
.
Condition
(
needsKeyPrefix
,
Expression
.
Call
(
member
,
prepend
,
keyPrefixValueArr
),
member
);
// keyArr[i++] = needsKeyPrefix ? objTyped.{member} : objTyped.{Member}.Prepend(prefixValue)
var
member
=
GetMember
(
objTyped
,
key
);
operations
.
Add
(
Expression
.
Assign
(
Expression
.
ArrayAccess
(
keyArr
,
Expression
.
Constant
(
i
++)
),
Expression
.
Condition
(
needsKeyPrefix
,
Expression
.
Call
(
member
,
s_prepend
,
prefixValueAsArgs
),
member
))
);
}
keysResult
=
Expression
.
NewArrayInit
(
typeof
(
RedisKey
),
keysResultArr
);
}
if
(
args
.
Count
==
0
)
{
// if there are no args, don't allocate
valuesResult
=
Expression
.
Constant
(
null
,
typeof
(
RedisValue
[]));
}
else
if
(
args
.
Count
!=
0
)
{
valuesResult
=
Expression
.
NewArrayInit
(
typeof
(
RedisValue
),
args
.
Select
(
arg
=>
// argArr = result.ArgsArray;
operations
.
Add
(
Expression
.
Assign
(
argArr
,
Expression
.
PropertyOrField
(
result
,
nameof
(
ScriptParameters
.
ArgArray
))));
int
i
=
0
;
foreach
(
var
arg
in
args
)
{
Expression
rhs
;
var
member
=
GetMember
(
objTyped
,
arg
);
if
(
member
.
Type
==
typeof
(
RedisValue
))
return
member
;
// pass-thru
if
(
member
.
Type
==
typeof
(
RedisKey
))
{
// need to apply prefix (note we can re-use the body from earlier)
var
val
=
keysResultArr
[
keys
.
IndexOf
(
arg
)];
return
Expression
.
Call
(
val
,
asRedisValue
);
if
(
member
.
Type
==
typeof
(
RedisValue
))
{
// ... = objTyped.{member}
rhs
=
member
;
// pass-thru
}
// otherwise: use the conversion operator
else
if
(
member
.
Type
==
typeof
(
RedisKey
))
{
int
keyIndex
=
keys
.
IndexOf
(
arg
);
Debug
.
Assert
(
keyIndex
>=
0
);
// ... = keys[{index}].AsRedisValue()
rhs
=
Expression
.
Call
(
Expression
.
ArrayAccess
(
keyArr
,
Expression
.
Constant
(
keyIndex
)),
s_asRedisValue
);
}
else
{
// ... = (SomeConversion)objTyped.{member}
var
conversion
=
_conversionOperators
[
member
.
Type
];
return
Expression
.
Call
(
conversion
,
member
);
}));
rhs
=
Expression
.
Call
(
conversion
,
member
);
}
// argArr[i++] = ...
operations
.
Add
(
Expression
.
Assign
(
Expression
.
ArrayAccess
(
argArr
,
Expression
.
Constant
(
i
++)),
rhs
));
}
}
operations
.
Add
(
result
);
// final operation: return result
var
body
=
Expression
.
Lambda
<
Func
<
object
,
RedisKey
?,
ScriptParameters
>>(
Expression
.
New
(
ScriptParameters
.
Cons
,
keysResult
,
valuesResult
),
objUntyped
,
keyPrefix
);
Expression
.
Block
(
typeof
(
ScriptParameters
),
// return type of the block
new
ParameterExpression
[]
{
objTyped
,
result
,
keyArr
,
argArr
,
needsPrefix
,
prefixValue
},
// locals scoped by the block
operations
),
// the operations to perform
objUntyped
,
keyPrefix
);
// parameters to the lambda
return
body
.
Compile
();
}
}
...
...
tests/StackExchange.Redis.Tests/Cluster.cs
View file @
217efc0f
...
...
@@ -164,7 +164,7 @@ public void TestIdentity()
public
void
IntentionalWrongServer
()
{
string
StringGet
(
IServer
server
,
RedisKey
key
,
CommandFlags
flags
=
CommandFlags
.
None
)
=>
(
string
)
server
.
Execute
(
"GET"
,
new
object
[]
{
key
},
flags
);
=>
(
string
)
server
.
Execute
(
"GET"
,
(
ReadOnlyMemory
<
object
>)
new
object
[]
{
key
},
flags
);
using
(
var
conn
=
Create
())
{
...
...
tests/StackExchange.Redis.Tests/Scripting.cs
View file @
217efc0f
...
...
@@ -603,10 +603,9 @@ public void LuaScriptWithKeys()
Assert
.
Equal
(
123
,
(
int
)
val
);
// no super clean way to extract this; so just abuse InternalsVisibleTo
script
.
ExtractParameters
(
p
,
null
,
out
RedisKey
[]
keys
,
out
RedisValue
[]
args
);
Assert
.
NotNull
(
keys
);
Assert
.
Single
(
keys
);
Assert
.
Equal
(
key
,
keys
[
0
]);
script
.
ExtractParameters
(
p
,
null
,
out
var
keys
,
out
var
args
);
Assert
.
Equal
(
1
,
keys
.
Length
);
Assert
.
Equal
(
key
,
keys
.
Span
[
0
]);
}
}
...
...
@@ -718,10 +717,9 @@ public void LoadedLuaScriptWithKeys()
Assert
.
Equal
(
123
,
(
int
)
val
);
// no super clean way to extract this; so just abuse InternalsVisibleTo
prepared
.
Original
.
ExtractParameters
(
p
,
null
,
out
RedisKey
[]
keys
,
out
RedisValue
[]
args
);
Assert
.
NotNull
(
keys
);
Assert
.
Single
(
keys
);
Assert
.
Equal
(
key
,
keys
[
0
]);
prepared
.
Original
.
ExtractParameters
(
p
,
null
,
out
var
keys
,
out
var
args
);
Assert
.
Equal
(
1
,
keys
.
Length
);
Assert
.
Equal
(
key
,
keys
.
Span
[
0
]);
}
}
...
...
@@ -821,13 +819,12 @@ public void LuaScriptPrefixedKeys()
var
p
=
new
{
key
=
(
RedisKey
)
key
,
value
=
"hello"
};
// no super clean way to extract this; so just abuse InternalsVisibleTo
prepared
.
ExtractParameters
(
p
,
"prefix-"
,
out
RedisKey
[]
keys
,
out
RedisValue
[]
args
);
Assert
.
NotNull
(
keys
);
Assert
.
Single
(
keys
);
Assert
.
Equal
(
"prefix-"
+
key
,
keys
[
0
]);
prepared
.
ExtractParameters
(
p
,
"prefix-"
,
out
var
keys
,
out
var
args
);
Assert
.
Equal
(
1
,
keys
.
Length
);
Assert
.
Equal
(
"prefix-"
+
key
,
keys
.
Span
[
0
]);
Assert
.
Equal
(
2
,
args
.
Length
);
Assert
.
Equal
(
"prefix-"
+
key
,
args
[
0
]);
Assert
.
Equal
(
"hello"
,
args
[
1
]);
Assert
.
Equal
(
"prefix-"
+
key
,
args
.
Span
[
0
]);
Assert
.
Equal
(
"hello"
,
args
.
Span
[
1
]);
}
[
Fact
]
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment