Skip to content

Commit fbf09b0

Browse files
authored
Merge pull request asynkron#257 from tomliversidge/253_cancel_receive_timeout
add CancelReceiveTimeout method on IContext and fix bug in SetReceive…
2 parents 4496871 + 0cd1535 commit fbf09b0

File tree

3 files changed

+129
-23
lines changed

3 files changed

+129
-23
lines changed

src/Proto.Actor/IContext.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,13 @@ public interface IContext : ISenderContext
9292
/// <summary>
9393
/// Sets the receive timeout. If no message is received for the given duration, a ReceiveTimeout message will be sent
9494
/// to the actor. If a message is received within the given duration, the timer is reset, unless the message implements
95-
/// INotInfluenceReceiveTimeout. Setting a duration of less than 1ms will disable the timer.
95+
/// INotInfluenceReceiveTimeout.
9696
/// </summary>
9797
/// <param name="duration">The receive timeout duration</param>
9898
void SetReceiveTimeout(TimeSpan duration);
9999

100+
void CancelReceiveTimeout();
101+
100102
Task ReceiveAsync(object message);
101103
void Tell(PID target, object message);
102104
void Request(PID target, object message);

src/Proto.Actor/LocalContext.cs

+22-22
Original file line numberDiff line numberDiff line change
@@ -144,32 +144,37 @@ public void Unwatch(PID pid)
144144

145145
public void SetReceiveTimeout(TimeSpan duration)
146146
{
147-
if (duration == ReceiveTimeout)
147+
if (duration <= TimeSpan.Zero)
148148
{
149-
return;
149+
throw new ArgumentOutOfRangeException(nameof(duration), duration, "Duration must be greater than zero");
150150
}
151-
if (duration > TimeSpan.Zero)
151+
152+
if (duration == ReceiveTimeout)
152153
{
153-
StopReceiveTimeout();
154+
return;
154155
}
155-
if (duration < TimeSpan.FromMilliseconds(1))
156+
157+
StopReceiveTimeout();
158+
ReceiveTimeout = duration;
159+
160+
if (_receiveTimeoutTimer == null)
156161
{
157-
duration = TimeSpan.FromMilliseconds(1);
162+
_receiveTimeoutTimer = new Timer(ReceiveTimeoutCallback, null, ReceiveTimeout, ReceiveTimeout);
158163
}
159-
ReceiveTimeout = duration;
160-
if (ReceiveTimeout > TimeSpan.Zero)
164+
else
161165
{
162-
if (_receiveTimeoutTimer == null)
163-
{
164-
_receiveTimeoutTimer = new Timer(ReceiveTimeoutCallback, null, ReceiveTimeout, ReceiveTimeout);
165-
}
166-
else
167-
{
168-
ResetReceiveTimeout();
169-
}
166+
ResetReceiveTimeout();
170167
}
171168
}
172169

170+
public void CancelReceiveTimeout()
171+
{
172+
if (_receiveTimeoutTimer == null) return;
173+
StopReceiveTimeout();
174+
_receiveTimeoutTimer = null;
175+
ReceiveTimeout = TimeSpan.Zero;
176+
}
177+
173178
public Task ReceiveAsync(object message)
174179
{
175180
return ProcessMessageAsync(message);
@@ -448,12 +453,7 @@ private async Task HandleStopAsync()
448453

449454
private async Task TryRestartOrTerminateAsync()
450455
{
451-
if (_receiveTimeoutTimer != null)
452-
{
453-
StopReceiveTimeout();
454-
_receiveTimeoutTimer = null;
455-
ReceiveTimeout = TimeSpan.Zero;
456-
}
456+
CancelReceiveTimeout();
457457

458458
if (_children?.Count > 0)
459459
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.Threading;
3+
using Xunit;
4+
5+
namespace Proto.Tests
6+
{
7+
public class ReceiveTimeoutTests
8+
{
9+
[Fact]
10+
public void receive_timeout_received_within_expected_time()
11+
{
12+
var timeoutReceived = false;
13+
var props = Actor.FromFunc((context) =>
14+
{
15+
switch (context.Message)
16+
{
17+
case Started _:
18+
context.SetReceiveTimeout(TimeSpan.FromMilliseconds(150));
19+
break;
20+
case ReceiveTimeout _:
21+
timeoutReceived = true;
22+
break;
23+
}
24+
return Actor.Done;
25+
});
26+
Actor.Spawn(props);
27+
28+
Thread.Sleep(500);
29+
Assert.True(timeoutReceived);
30+
}
31+
32+
[Fact]
33+
public void receive_timeout_not_received_within_expected_time()
34+
{
35+
var timeoutReceived = false;
36+
var props = Actor.FromFunc((context) =>
37+
{
38+
switch (context.Message)
39+
{
40+
case Started _:
41+
context.SetReceiveTimeout(TimeSpan.FromMilliseconds(1500));
42+
break;
43+
case ReceiveTimeout _:
44+
timeoutReceived = true;
45+
break;
46+
}
47+
return Actor.Done;
48+
});
49+
Actor.Spawn(props);
50+
51+
Thread.Sleep(500);
52+
Assert.False(timeoutReceived);
53+
}
54+
55+
[Fact]
56+
public void can_cancel_receive_timeout()
57+
{
58+
var timeoutReceived = false;
59+
var props = Actor.FromFunc((context) =>
60+
{
61+
switch (context.Message)
62+
{
63+
case Started _:
64+
context.SetReceiveTimeout(TimeSpan.FromMilliseconds(150));
65+
context.CancelReceiveTimeout();
66+
break;
67+
case ReceiveTimeout _:
68+
timeoutReceived = true;
69+
break;
70+
}
71+
return Actor.Done;
72+
});
73+
Actor.Spawn(props);
74+
75+
Thread.Sleep(500);
76+
Assert.False(timeoutReceived);
77+
}
78+
79+
[Fact]
80+
public void can_still_set_receive_timeout_after_cancelling()
81+
{
82+
var timeoutReceived = false;
83+
var props = Actor.FromFunc((context) =>
84+
{
85+
switch (context.Message)
86+
{
87+
case Started _:
88+
context.SetReceiveTimeout(TimeSpan.FromMilliseconds(150));
89+
context.CancelReceiveTimeout();
90+
context.SetReceiveTimeout(TimeSpan.FromMilliseconds(150));
91+
break;
92+
case ReceiveTimeout _:
93+
timeoutReceived = true;
94+
break;
95+
}
96+
return Actor.Done;
97+
});
98+
Actor.Spawn(props);
99+
100+
Thread.Sleep(500);
101+
Assert.True(timeoutReceived);
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)