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
c6af6cf0
Commit
c6af6cf0
authored
Jul 05, 2018
by
Marc Gravell
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fully working now; renamed to ChannelMessageQueue
parent
1c2f2834
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
116 additions
and
175 deletions
+116
-175
BasicOps.cs
StackExchange.Redis.Tests/BasicOps.cs
+4
-7
PubSub.cs
StackExchange.Redis.Tests/Booksleeve/PubSub.cs
+7
-10
TaskTests.cs
StackExchange.Redis.Tests/TaskTests.cs
+0
-122
ChannelMessageQueue.cs
...Exchange.Redis/StackExchange/Redis/ChannelMessageQueue.cs
+34
-5
ISubscriber.cs
...hange.Redis/StackExchange/Redis/Interfaces/ISubscriber.cs
+2
-2
MessageCompletable.cs
...kExchange.Redis/StackExchange/Redis/MessageCompletable.cs
+27
-12
RedisSubscriber.cs
StackExchange.Redis/StackExchange/Redis/RedisSubscriber.cs
+42
-17
No files found.
StackExchange.Redis.Tests/BasicOps.cs
View file @
c6af6cf0
...
@@ -301,22 +301,19 @@ public void TestQuit()
...
@@ -301,22 +301,19 @@ public void TestQuit()
Assert
.
Throws
<
RedisConnectionException
>(()
=>
db
.
Ping
());
Assert
.
Throws
<
RedisConnectionException
>(()
=>
db
.
Ping
());
watch
.
Stop
();
watch
.
Stop
();
Output
.
WriteLine
(
"Time to notice quit: {0}ms ({1})"
,
watch
.
ElapsedMilliseconds
,
Output
.
WriteLine
(
"Time to notice quit: {0}ms ({1})"
,
watch
.
ElapsedMilliseconds
,
preserveOrder
?
"preserve order"
:
"any order"
);
"any order"
);
Thread
.
Sleep
(
20
);
Thread
.
Sleep
(
20
);
Debug
.
WriteLine
(
"Pinging..."
);
Debug
.
WriteLine
(
"Pinging..."
);
Assert
.
Equal
(
key
,
(
string
)
db
.
StringGet
(
key
));
Assert
.
Equal
(
key
,
(
string
)
db
.
StringGet
(
key
));
}
}
}
}
[
Theory
]
[
Fact
]
[
InlineData
(
true
)]
public
async
Task
TestSevered
()
[
InlineData
(
false
)]
public
async
Task
TestSevered
(
bool
preserveOrder
)
{
{
SetExpectedAmbientFailureCount
(
2
);
SetExpectedAmbientFailureCount
(
2
);
using
(
var
muxer
=
Create
(
allowAdmin
:
true
))
using
(
var
muxer
=
Create
(
allowAdmin
:
true
))
{
{
muxer
.
PreserveAsyncOrder
=
preserveOrder
;
var
db
=
muxer
.
GetDatabase
();
var
db
=
muxer
.
GetDatabase
();
string
key
=
Guid
.
NewGuid
().
ToString
();
string
key
=
Guid
.
NewGuid
().
ToString
();
db
.
KeyDelete
(
key
,
CommandFlags
.
FireAndForget
);
db
.
KeyDelete
(
key
,
CommandFlags
.
FireAndForget
);
...
@@ -326,7 +323,7 @@ public async Task TestSevered(bool preserveOrder)
...
@@ -326,7 +323,7 @@ public async Task TestSevered(bool preserveOrder)
db
.
Ping
();
db
.
Ping
();
watch
.
Stop
();
watch
.
Stop
();
Output
.
WriteLine
(
"Time to re-establish: {0}ms ({1})"
,
watch
.
ElapsedMilliseconds
,
Output
.
WriteLine
(
"Time to re-establish: {0}ms ({1})"
,
watch
.
ElapsedMilliseconds
,
preserveOrder
?
"preserve order"
:
"any order"
);
"any order"
);
await
Task
.
Delay
(
2000
).
ForAwait
();
await
Task
.
Delay
(
2000
).
ForAwait
();
Debug
.
WriteLine
(
"Pinging..."
);
Debug
.
WriteLine
(
"Pinging..."
);
Assert
.
Equal
(
key
,
db
.
StringGet
(
key
));
Assert
.
Equal
(
key
,
db
.
StringGet
(
key
));
...
...
StackExchange.Redis.Tests/Booksleeve/PubSub.cs
View file @
c6af6cf0
...
@@ -135,8 +135,7 @@ public async Task PubSubGetAllCorrectOrder()
...
@@ -135,8 +135,7 @@ public async Task PubSubGetAllCorrectOrder()
const
int
count
=
500000
;
const
int
count
=
500000
;
var
syncLock
=
new
object
();
var
syncLock
=
new
object
();
var
dataList
=
new
List
<
int
>(
count
);
var
data
=
new
List
<
int
>(
count
);
var
dataHash
=
new
HashSet
<
int
>();
var
subChannel
=
await
sub
.
SubscribeAsync
(
channel
);
var
subChannel
=
await
sub
.
SubscribeAsync
(
channel
);
await
sub
.
PingAsync
();
await
sub
.
PingAsync
();
...
@@ -147,12 +146,11 @@ async Task RunLoop()
...
@@ -147,12 +146,11 @@ async Task RunLoop()
{
{
var
work
=
await
subChannel
.
ReadAsync
();
var
work
=
await
subChannel
.
ReadAsync
();
int
i
=
int
.
Parse
(
Encoding
.
UTF8
.
GetString
(
work
.
Value
));
int
i
=
int
.
Parse
(
Encoding
.
UTF8
.
GetString
(
work
.
Value
));
lock
(
data
List
)
lock
(
data
)
{
{
dataList
.
Add
(
i
);
data
.
Add
(
i
);
dataHash
.
Add
(
i
);
if
(
data
.
Count
==
count
)
break
;
if
(
dataList
.
Count
==
count
)
break
;
if
((
data
.
Count
%
10
)
==
99
)
Output
.
WriteLine
(
data
.
Count
.
ToString
());
if
((
dataList
.
Count
%
10
)
==
99
)
Output
.
WriteLine
(
dataList
.
Count
.
ToString
());
}
}
}
}
lock
(
syncLock
)
lock
(
syncLock
)
...
@@ -172,12 +170,11 @@ async Task RunLoop()
...
@@ -172,12 +170,11 @@ async Task RunLoop()
// subChannel.Unsubscribe();
// subChannel.Unsubscribe();
if
(!
Monitor
.
Wait
(
syncLock
,
20000
))
if
(!
Monitor
.
Wait
(
syncLock
,
20000
))
{
{
throw
new
TimeoutException
(
"Items: "
+
data
List
.
Count
);
throw
new
TimeoutException
(
"Items: "
+
data
.
Count
);
}
}
for
(
int
i
=
0
;
i
<
count
;
i
++)
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
{
Assert
.
Contains
(
i
,
dataHash
);
Assert
.
Equal
(
i
,
data
[
i
]);
// Assert.Equal(i, data[i]);
}
}
}
}
}
}
...
...
StackExchange.Redis.Tests/TaskTests.cs
deleted
100644 → 0
View file @
1c2f2834
using
System
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
Xunit
;
namespace
StackExchange.Redis.Tests
{
public
class
TaskTests
{
#if DEBUG
[
Theory
]
[
InlineData
(
SourceOrign
.
NewTCS
)]
[
InlineData
(
SourceOrign
.
Create
)]
public
void
VerifyIsSyncSafe
(
SourceOrign
origin
)
{
var
source
=
Create
<
int
>(
origin
);
// Yes this looks stupid, but it's the proper pattern for how we statically init now
// ...and if we're dropping NET45 support, we can just nuke it all.
#if NET462
Assert
.
True
(
TaskSource
.
IsSyncSafe
(
source
.
Task
));
#elif NETCOREAPP2_0
Assert
.
True
(
TaskSource
.
IsSyncSafe
(
source
.
Task
));
#endif
}
private
static
TaskCompletionSource
<
T
>
Create
<
T
>(
SourceOrign
origin
)
{
switch
(
origin
)
{
case
SourceOrign
.
NewTCS
:
return
new
TaskCompletionSource
<
T
>();
case
SourceOrign
.
Create
:
return
TaskSource
.
Create
<
T
>(
null
);
default
:
throw
new
ArgumentOutOfRangeException
(
nameof
(
origin
));
}
}
[
Theory
]
// regular framework behaviour: 2 out of 3 cause hijack
[
InlineData
(
SourceOrign
.
NewTCS
,
AttachMode
.
ContinueWith
,
true
)]
[
InlineData
(
SourceOrign
.
NewTCS
,
AttachMode
.
ContinueWithExecSync
,
false
)]
[
InlineData
(
SourceOrign
.
NewTCS
,
AttachMode
.
Await
,
true
)]
// Create is just a wrapper of ^^^; expect the same
[
InlineData
(
SourceOrign
.
Create
,
AttachMode
.
ContinueWith
,
true
)]
[
InlineData
(
SourceOrign
.
Create
,
AttachMode
.
ContinueWithExecSync
,
false
)]
[
InlineData
(
SourceOrign
.
Create
,
AttachMode
.
Await
,
true
)]
public
void
TestContinuationHijacking
(
SourceOrign
origin
,
AttachMode
attachMode
,
bool
expectHijack
)
{
TaskCompletionSource
<
int
>
source
=
Create
<
int
>(
origin
);
int
settingThread
=
Environment
.
CurrentManagedThreadId
;
var
state
=
new
AwaitState
();
state
.
Attach
(
source
.
Task
,
attachMode
);
source
.
TrySetResult
(
123
);
state
.
Wait
();
// waits for the continuation to run
int
from
=
state
.
Thread
;
Assert
.
NotEqual
(-
1
,
from
);
// not set
if
(
expectHijack
)
{
Assert
.
True
(
settingThread
!=
from
,
$"expected hijack; didn't happen, Origin=
{
settingThread
}
, Final=
{
from
}
"
);
}
else
{
Assert
.
True
(
settingThread
==
from
,
$"setter was hijacked, Origin=
{
settingThread
}
, Final=
{
from
}
"
);
}
}
public
enum
SourceOrign
{
NewTCS
,
Create
}
public
enum
AttachMode
{
ContinueWith
,
ContinueWithExecSync
,
Await
}
private
class
AwaitState
{
public
int
Thread
=>
continuationThread
;
private
volatile
int
continuationThread
=
-
1
;
private
readonly
ManualResetEventSlim
evt
=
new
ManualResetEventSlim
();
public
void
Wait
()
{
if
(!
evt
.
Wait
(
5000
))
throw
new
TimeoutException
();
}
public
void
Attach
(
Task
task
,
AttachMode
attachMode
)
{
switch
(
attachMode
)
{
case
AttachMode
.
ContinueWith
:
task
.
ContinueWith
(
Continue
);
break
;
case
AttachMode
.
ContinueWithExecSync
:
task
.
ContinueWith
(
Continue
,
TaskContinuationOptions
.
ExecuteSynchronously
);
break
;
case
AttachMode
.
Await
:
DoAwait
(
task
);
break
;
default
:
throw
new
ArgumentOutOfRangeException
(
nameof
(
attachMode
));
}
}
private
void
Continue
(
Task
task
)
{
continuationThread
=
Environment
.
CurrentManagedThreadId
;
evt
.
Set
();
}
private
async
void
DoAwait
(
Task
task
)
{
await
task
.
ConfigureAwait
(
false
);
continuationThread
=
Environment
.
CurrentManagedThreadId
;
evt
.
Set
();
}
}
#endif
}
}
StackExchange.Redis/StackExchange/Redis/ChannelMessage.cs
→
StackExchange.Redis/StackExchange/Redis/ChannelMessage
Queue
.cs
View file @
c6af6cf0
...
@@ -30,7 +30,7 @@ internal ChannelMessage(RedisChannel channel, RedisValue value)
...
@@ -30,7 +30,7 @@ internal ChannelMessage(RedisChannel channel, RedisValue value)
/// <summary>
/// <summary>
/// Represents a message queue of pub/sub notifications
/// Represents a message queue of pub/sub notifications
/// </summary>
/// </summary>
public
sealed
class
ChannelMessage
Channel
public
sealed
class
ChannelMessage
Queue
{
{
private
readonly
Channel
<
ChannelMessage
>
_channel
;
private
readonly
Channel
<
ChannelMessage
>
_channel
;
private
readonly
RedisChannel
_redisChannel
;
private
readonly
RedisChannel
_redisChannel
;
...
@@ -41,19 +41,36 @@ public sealed class ChannelMessageChannel
...
@@ -41,19 +41,36 @@ public sealed class ChannelMessageChannel
/// </summary>
/// </summary>
public
bool
IsComplete
{
get
;
private
set
;
}
public
bool
IsComplete
{
get
;
private
set
;
}
internal
ChannelMessage
Channel
(
RedisChannel
redisChannel
,
ISubscriber
parent
)
internal
ChannelMessage
Queue
(
RedisChannel
redisChannel
,
ISubscriber
parent
)
{
{
_redisChannel
=
redisChannel
;
_redisChannel
=
redisChannel
;
_parent
=
parent
;
_parent
=
parent
;
_channel
=
Channel
.
CreateUnbounded
<
ChannelMessage
>();
_channel
=
Channel
.
CreateUnbounded
<
ChannelMessage
>(
s_ChannelOptions
);
_channel
.
Reader
.
Completion
.
ContinueWith
(
_channel
.
Reader
.
Completion
.
ContinueWith
(
(
t
,
state
)
=>
((
ChannelMessage
Channel
)
state
).
IsComplete
=
true
,
this
,
TaskContinuationOptions
.
ExecuteSynchronously
);
(
t
,
state
)
=>
((
ChannelMessage
Queue
)
state
).
IsComplete
=
true
,
this
,
TaskContinuationOptions
.
ExecuteSynchronously
);
}
}
static
readonly
UnboundedChannelOptions
s_ChannelOptions
=
new
UnboundedChannelOptions
{
SingleWriter
=
true
,
SingleReader
=
false
,
AllowSynchronousContinuations
=
false
,
};
internal
void
Subscribe
(
CommandFlags
flags
)
=>
_parent
.
Subscribe
(
_redisChannel
,
HandleMessage
,
flags
);
internal
void
Subscribe
(
CommandFlags
flags
)
=>
_parent
.
Subscribe
(
_redisChannel
,
HandleMessage
,
flags
);
internal
Task
SubscribeAsync
(
CommandFlags
flags
)
=>
_parent
.
SubscribeAsync
(
_redisChannel
,
HandleMessage
,
flags
);
internal
Task
SubscribeAsync
(
CommandFlags
flags
)
=>
_parent
.
SubscribeAsync
(
_redisChannel
,
HandleMessage
,
flags
);
private
void
HandleMessage
(
RedisChannel
channel
,
RedisValue
value
)
private
void
HandleMessage
(
RedisChannel
channel
,
RedisValue
value
)
=>
_channel
.
Writer
.
TryWrite
(
new
ChannelMessage
(
channel
,
value
));
{
var
writer
=
_channel
.
Writer
;
if
(
channel
.
IsNull
&&
value
.
IsNull
)
// see ForSyncShutdown
{
writer
.
TryComplete
();
}
else
{
writer
.
TryWrite
(
new
ChannelMessage
(
channel
,
value
));
}
}
/// <summary>
/// <summary>
/// Consume a message from the channel
/// Consume a message from the channel
...
@@ -82,6 +99,18 @@ internal async Task UnsubscribeAsyncImpl(Exception error = null, CommandFlags fl
...
@@ -82,6 +99,18 @@ internal async Task UnsubscribeAsyncImpl(Exception error = null, CommandFlags fl
}
}
}
}
internal
static
bool
IsOneOf
(
Action
<
RedisChannel
,
RedisValue
>
handler
)
{
try
{
return
handler
!=
null
&&
handler
.
Target
is
ChannelMessageQueue
&&
handler
.
Method
.
Name
==
nameof
(
HandleMessage
);
}
catch
{
return
false
;
}
}
/// <summary>
/// <summary>
/// Stop receiving messages on this channel
/// Stop receiving messages on this channel
/// </summary>
/// </summary>
...
...
StackExchange.Redis/StackExchange/Redis/Interfaces/ISubscriber.cs
View file @
c6af6cf0
...
@@ -73,7 +73,7 @@ public interface ISubscriber : IRedis
...
@@ -73,7 +73,7 @@ public interface ISubscriber : IRedis
/// <returns>A channel that represents this source</returns>
/// <returns>A channel that represents this source</returns>
/// <remarks>https://redis.io/commands/subscribe</remarks>
/// <remarks>https://redis.io/commands/subscribe</remarks>
/// <remarks>https://redis.io/commands/psubscribe</remarks>
/// <remarks>https://redis.io/commands/psubscribe</remarks>
ChannelMessage
Channel
Subscribe
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
);
ChannelMessage
Queue
Subscribe
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// <summary>
/// Subscribe to perform some operation when a change to the preferred/active node is broadcast.
/// Subscribe to perform some operation when a change to the preferred/active node is broadcast.
...
@@ -93,7 +93,7 @@ public interface ISubscriber : IRedis
...
@@ -93,7 +93,7 @@ public interface ISubscriber : IRedis
/// <returns>A channel that represents this source</returns>
/// <returns>A channel that represents this source</returns>
/// <remarks>https://redis.io/commands/subscribe</remarks>
/// <remarks>https://redis.io/commands/subscribe</remarks>
/// <remarks>https://redis.io/commands/psubscribe</remarks>
/// <remarks>https://redis.io/commands/psubscribe</remarks>
Task
<
ChannelMessage
Channel
>
SubscribeAsync
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
);
Task
<
ChannelMessage
Queue
>
SubscribeAsync
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
);
/// <summary>
/// <summary>
/// Inidicate to which redis server we are actively subscribed for a given channel; returns null if
/// Inidicate to which redis server we are actively subscribed for a given channel; returns null if
...
...
StackExchange.Redis/StackExchange/Redis/MessageCompletable.cs
View file @
c6af6cf0
...
@@ -7,35 +7,50 @@ internal sealed class MessageCompletable : ICompletable
...
@@ -7,35 +7,50 @@ internal sealed class MessageCompletable : ICompletable
{
{
private
readonly
RedisChannel
channel
;
private
readonly
RedisChannel
channel
;
private
readonly
Action
<
RedisChannel
,
RedisValue
>
h
andler
;
private
readonly
Action
<
RedisChannel
,
RedisValue
>
syncHandler
,
asyncH
andler
;
private
readonly
RedisValue
message
;
private
readonly
RedisValue
message
;
public
MessageCompletable
(
RedisChannel
channel
,
RedisValue
message
,
Action
<
RedisChannel
,
RedisValue
>
syncHandler
,
Action
<
RedisChannel
,
RedisValue
>
asyncHandler
)
public
MessageCompletable
(
RedisChannel
channel
,
RedisValue
message
,
Action
<
RedisChannel
,
RedisValue
>
handler
)
{
{
this
.
channel
=
channel
;
this
.
channel
=
channel
;
this
.
message
=
message
;
this
.
message
=
message
;
this
.
handler
=
handler
;
this
.
syncHandler
=
syncHandler
;
this
.
asyncHandler
=
asyncHandler
;
}
}
public
override
string
ToString
()
=>
(
string
)
channel
;
public
override
string
ToString
()
=>
(
string
)
channel
;
public
bool
TryComplete
(
bool
isAsync
)
public
bool
TryComplete
(
bool
isAsync
)
{
{
if
(
handler
==
null
)
return
true
;
if
(
isAsync
)
if
(
isAsync
)
{
{
ConnectionMultiplexer
.
TraceWithoutContext
(
"Invoking...: "
+
(
string
)
channel
,
"Subscription"
);
if
(
asyncHandler
!=
null
)
foreach
(
Action
<
RedisChannel
,
RedisValue
>
sub
in
handler
.
GetInvocationList
())
{
ConnectionMultiplexer
.
TraceWithoutContext
(
"Invoking (async)...: "
+
(
string
)
channel
,
"Subscription"
);
foreach
(
Action
<
RedisChannel
,
RedisValue
>
sub
in
asyncHandler
.
GetInvocationList
())
{
{
try
{
sub
.
Invoke
(
channel
,
message
);
}
try
{
sub
.
Invoke
(
channel
,
message
);
}
catch
{
}
catch
{
}
}
}
ConnectionMultiplexer
.
TraceWithoutContext
(
"Invoke complete"
,
"Subscription"
);
ConnectionMultiplexer
.
TraceWithoutContext
(
"Invoke complete (async)"
,
"Subscription"
);
}
return
true
;
return
true
;
}
}
// needs to be called async (unless there is nothing to do!)
else
return
false
;
{
if
(
syncHandler
!=
null
)
{
ConnectionMultiplexer
.
TraceWithoutContext
(
"Invoking (sync)...: "
+
(
string
)
channel
,
"Subscription"
);
foreach
(
Action
<
RedisChannel
,
RedisValue
>
sub
in
syncHandler
.
GetInvocationList
())
{
try
{
sub
.
Invoke
(
channel
,
message
);
}
catch
{
}
}
ConnectionMultiplexer
.
TraceWithoutContext
(
"Invoke complete (sync)"
,
"Subscription"
);
}
return
asyncHandler
!=
null
;
// anything async to do?
}
}
}
void
ICompletable
.
AppendStormLog
(
StringBuilder
sb
)
void
ICompletable
.
AppendStormLog
(
StringBuilder
sb
)
...
...
StackExchange.Redis/StackExchange/Redis/RedisSubscriber.cs
View file @
c6af6cf0
...
@@ -31,15 +31,16 @@ internal Task AddSubscription(RedisChannel channel, Action<RedisChannel, RedisVa
...
@@ -31,15 +31,16 @@ internal Task AddSubscription(RedisChannel channel, Action<RedisChannel, RedisVa
{
{
if
(
handler
!=
null
)
if
(
handler
!=
null
)
{
{
bool
asAsync
=
!
ChannelMessageQueue
.
IsOneOf
(
handler
);
lock
(
subscriptions
)
lock
(
subscriptions
)
{
{
if
(
subscriptions
.
TryGetValue
(
channel
,
out
Subscription
sub
))
if
(
subscriptions
.
TryGetValue
(
channel
,
out
Subscription
sub
))
{
{
sub
.
Add
(
handler
);
sub
.
Add
(
asAsync
,
handler
);
}
}
else
else
{
{
sub
=
new
Subscription
(
handler
);
sub
=
new
Subscription
(
asAsync
,
handler
);
subscriptions
.
Add
(
channel
,
sub
);
subscriptions
.
Add
(
channel
,
sub
);
var
task
=
sub
.
SubscribeToServer
(
this
,
channel
,
flags
,
asyncState
,
false
);
var
task
=
sub
.
SubscribeToServer
(
this
,
channel
,
flags
,
asyncState
,
false
);
if
(
task
!=
null
)
return
task
;
if
(
task
!=
null
)
return
task
;
...
@@ -84,7 +85,14 @@ internal Task RemoveAllSubscriptions(CommandFlags flags, object asyncState)
...
@@ -84,7 +85,14 @@ internal Task RemoveAllSubscriptions(CommandFlags flags, object asyncState)
{
{
foreach
(
var
pair
in
subscriptions
)
foreach
(
var
pair
in
subscriptions
)
{
{
pair
.
Value
.
Remove
(
null
);
// always wipes
var
msg
=
pair
.
Value
.
ForSyncShutdown
();
if
(
msg
!=
null
)
{
// TODO: execute
}
pair
.
Value
.
Remove
(
true
,
null
);
pair
.
Value
.
Remove
(
false
,
null
);
var
task
=
pair
.
Value
.
UnsubscribeFromServer
(
pair
.
Key
,
flags
,
asyncState
,
false
);
var
task
=
pair
.
Value
.
UnsubscribeFromServer
(
pair
.
Key
,
flags
,
asyncState
,
false
);
if
(
task
!=
null
)
last
=
task
;
if
(
task
!=
null
)
last
=
task
;
}
}
...
@@ -97,7 +105,8 @@ internal Task RemoveSubscription(RedisChannel channel, Action<RedisChannel, Redi
...
@@ -97,7 +105,8 @@ internal Task RemoveSubscription(RedisChannel channel, Action<RedisChannel, Redi
{
{
lock
(
subscriptions
)
lock
(
subscriptions
)
{
{
if
(
subscriptions
.
TryGetValue
(
channel
,
out
Subscription
sub
)
&&
sub
.
Remove
(
handler
))
bool
asAsync
=
ChannelMessageQueue
.
IsOneOf
(
handler
);
if
(
subscriptions
.
TryGetValue
(
channel
,
out
Subscription
sub
)
&&
sub
.
Remove
(
asAsync
,
handler
))
{
{
subscriptions
.
Remove
(
channel
);
subscriptions
.
Remove
(
channel
);
var
task
=
sub
.
UnsubscribeFromServer
(
channel
,
flags
,
asyncState
,
false
);
var
task
=
sub
.
UnsubscribeFromServer
(
channel
,
flags
,
asyncState
,
false
);
...
@@ -143,30 +152,46 @@ internal long ValidateSubscriptions()
...
@@ -143,30 +152,46 @@ internal long ValidateSubscriptions()
private
sealed
class
Subscription
private
sealed
class
Subscription
{
{
private
Action
<
RedisChannel
,
RedisValue
>
h
andler
;
private
Action
<
RedisChannel
,
RedisValue
>
_asyncHandler
,
_syncH
andler
;
private
ServerEndPoint
owner
;
private
ServerEndPoint
owner
;
public
Subscription
(
Action
<
RedisChannel
,
RedisValue
>
value
)
=>
handler
=
value
;
public
Subscription
(
bool
asAsync
,
Action
<
RedisChannel
,
RedisValue
>
value
)
{
if
(
asAsync
)
_asyncHandler
=
value
;
else
_syncHandler
=
value
;
}
public
void
Add
(
Action
<
RedisChannel
,
RedisValue
>
value
)
=>
handler
+=
value
;
public
void
Add
(
bool
asAsync
,
Action
<
RedisChannel
,
RedisValue
>
value
)
{
if
(
asAsync
)
_asyncHandler
+=
value
;
else
_syncHandler
+=
value
;
}
public
ICompletable
ForSyncShutdown
()
{
var
syncHandler
=
_syncHandler
;
return
syncHandler
==
null
?
null
:
new
MessageCompletable
(
default
,
default
,
syncHandler
,
null
);
}
public
ICompletable
ForInvoke
(
RedisChannel
channel
,
RedisValue
message
)
public
ICompletable
ForInvoke
(
RedisChannel
channel
,
RedisValue
message
)
{
{
var
tmp
=
handler
;
var
syncHandler
=
_syncHandler
;
return
tmp
==
null
?
null
:
new
MessageCompletable
(
channel
,
message
,
tmp
);
var
asyncHandler
=
_asyncHandler
;
return
(
syncHandler
==
null
&&
asyncHandler
==
null
)
?
null
:
new
MessageCompletable
(
channel
,
message
,
syncHandler
,
asyncHandler
);
}
}
public
bool
Remove
(
Action
<
RedisChannel
,
RedisValue
>
value
)
public
bool
Remove
(
bool
asAsync
,
Action
<
RedisChannel
,
RedisValue
>
value
)
{
{
if
(
value
==
null
)
if
(
value
==
null
)
{
// treat as blanket wipe
{
// treat as blanket wipe
h
andler
=
null
;
if
(
asAsync
)
_asyncH
andler
=
null
;
return
true
;
else
_syncHandler
=
null
;
}
}
else
else
{
{
return
(
handler
-=
value
)
==
null
;
if
(
asAsync
)
_asyncHandler
-=
value
;
else
_syncHandler
-=
value
;
}
}
return
_syncHandler
==
null
&&
_asyncHandler
==
null
;
}
}
public
Task
SubscribeToServer
(
ConnectionMultiplexer
multiplexer
,
RedisChannel
channel
,
CommandFlags
flags
,
object
asyncState
,
bool
internalCall
)
public
Task
SubscribeToServer
(
ConnectionMultiplexer
multiplexer
,
RedisChannel
channel
,
CommandFlags
flags
,
object
asyncState
,
bool
internalCall
)
...
@@ -287,9 +312,9 @@ public void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> han
...
@@ -287,9 +312,9 @@ public void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> han
if
((
flags
&
CommandFlags
.
FireAndForget
)
==
0
)
Wait
(
task
);
if
((
flags
&
CommandFlags
.
FireAndForget
)
==
0
)
Wait
(
task
);
}
}
public
ChannelMessage
Channel
Subscribe
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
)
public
ChannelMessage
Queue
Subscribe
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
{
var
c
=
new
ChannelMessage
Channel
(
channel
,
this
);
var
c
=
new
ChannelMessage
Queue
(
channel
,
this
);
c
.
Subscribe
(
flags
);
c
.
Subscribe
(
flags
);
return
c
;
return
c
;
}
}
...
@@ -300,9 +325,9 @@ public Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue
...
@@ -300,9 +325,9 @@ public Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue
return
multiplexer
.
AddSubscription
(
channel
,
handler
,
flags
,
asyncState
);
return
multiplexer
.
AddSubscription
(
channel
,
handler
,
flags
,
asyncState
);
}
}
public
async
Task
<
ChannelMessage
Channel
>
SubscribeAsync
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
)
public
async
Task
<
ChannelMessage
Queue
>
SubscribeAsync
(
RedisChannel
channel
,
CommandFlags
flags
=
CommandFlags
.
None
)
{
{
var
c
=
new
ChannelMessage
Channel
(
channel
,
this
);
var
c
=
new
ChannelMessage
Queue
(
channel
,
this
);
await
c
.
SubscribeAsync
(
flags
);
await
c
.
SubscribeAsync
(
flags
);
return
c
;
return
c
;
}
}
...
...
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