Reputation: 1924
I've a specific requirement to only allow 5 simultaneous access to a WebAPI (.NET 4.5 specific) method (as part of a licensing plan). When the 5 simultaneous limit is reached, the webapi should reject any further attempts from ANY user.
I've created a Singleton class to count the number of times the method is accessed, and would decrement the counter after processing done,, and would do a check if the count > 5.
[HttpGet]
[Route("GetOrderStatus/{id}")]
public async Task<string> GetOrderStatus(int id)
{
GlobalState state = GlobalState.Instance;
string result;
if (state.OrderStatus > 5)
{
Log.Debug("INVALID - state.OrderStatus: " + state.OrderStatus.ToString() + ", ID: " + id.ToString());
return await Task.FromResult("INVALID");
}
else
{
state.OrderStatus++;
Log.Debug("state.OrderStatus: " + state.OrderStatus.ToString() + ", ID: " + id.ToString());
result = DoGetOrder(id);
state.OrderStatus--;
}
return await Task.FromResult(result);
}
private string DoGetOrder(int OrderTypeId)
{
Thread.Sleep(5000);
return "OK_" + OrderTypeId.ToString();
}
My singleton class is below.
public sealed class GlobalState
{
private static readonly Lazy<GlobalState> lazy = new Lazy<GlobalState>(() => new GlobalState());
public int OrderStatus { get; set; }
public int PlaceOrder { get; set; }
public static GlobalState Instance { get { return lazy.Value; } }
private GlobalState()
{
}
}
Unit Test.
using Flurl.Http;
[TestMethod]
public void GetOrderStatus_Returns_Valid()
{
StringBuilder sb = new StringBuilder();
int success = 0;
int failed = 0;
Parallel.For(0, 100, i =>
{
string url = "http://localhost:61803/api/getorderstatus/" + i.ToString();
var responseString = url.GetStringAsync().Result;
sb.AppendLine(responseString + ", ");
if (responseString.Contains("INVALID"))
{
failed++;
}
else
{
success++;
}
});
}
The results of the test is getting wrong. Log reproduced below.
2018-11-26 10:00:29.431 +00:00 [DBG] state.OrderStatus: 2, ID: 0
2018-11-26 10:00:29.432 +00:00 [DBG] state.OrderStatus: 3, ID: 25
2018-11-26 10:00:29.434 +00:00 [DBG] state.OrderStatus: 4, ID: 50
2018-11-26 10:00:30.310 +00:00 [DBG] state.OrderStatus: 5, ID: 75
2018-11-26 10:00:31.311 +00:00 [DBG] state.OrderStatus: 6, ID: 1
2018-11-26 10:00:32.313 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 26
2018-11-26 10:00:33.311 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 27
2018-11-26 10:00:33.312 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 51
2018-11-26 10:00:34.308 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 28
2018-11-26 10:00:34.308 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 52
2018-11-26 10:00:34.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 76
2018-11-26 10:00:35.309 +00:00 [DBG] state.OrderStatus: 4, ID: 3
2018-11-26 10:00:35.309 +00:00 [DBG] state.OrderStatus: 5, ID: 34
2018-11-26 10:00:35.309 +00:00 [DBG] state.OrderStatus: 4, ID: 30
2018-11-26 10:00:36.309 +00:00 [DBG] state.OrderStatus: 5, ID: 54
2018-11-26 10:00:36.309 +00:00 [DBG] state.OrderStatus: 5, ID: 79
2018-11-26 10:00:36.309 +00:00 [DBG] state.OrderStatus: 6, ID: 77
2018-11-26 10:00:36.309 +00:00 [DBG] state.OrderStatus: 6, ID: 53
2018-11-26 10:00:36.311 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 2
2018-11-26 10:00:36.321 +00:00 [DBG] state.OrderStatus: 6, ID: 29
2018-11-26 10:00:37.309 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 5
2018-11-26 10:00:37.309 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 56
2018-11-26 10:00:37.311 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 7
2018-11-26 10:00:38.312 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 8
2018-11-26 10:00:39.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 57
2018-11-26 10:00:39.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 6
2018-11-26 10:00:39.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 10
2018-11-26 10:00:39.311 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 81
2018-11-26 10:00:39.312 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 9
2018-11-26 10:00:40.355 +00:00 [DBG] state.OrderStatus: 4, ID: 14
2018-11-26 10:00:40.356 +00:00 [DBG] state.OrderStatus: 5, ID: 4
2018-11-26 10:00:40.357 +00:00 [DBG] state.OrderStatus: 6, ID: 35
2018-11-26 10:00:40.357 +00:00 [DBG] state.OrderStatus: 6, ID: 36
2018-11-26 10:00:40.357 +00:00 [DBG] state.OrderStatus: 6, ID: 58
2018-11-26 10:00:40.359 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 18
2018-11-26 10:00:42.310 +00:00 [DBG] state.OrderStatus: 3, ID: 78
2018-11-26 10:00:42.310 +00:00 [DBG] state.OrderStatus: 3, ID: 84
2018-11-26 10:00:42.310 +00:00 [DBG] state.OrderStatus: 2, ID: 59
2018-11-26 10:00:42.311 +00:00 [DBG] state.OrderStatus: 4, ID: 31
2018-11-26 10:00:42.311 +00:00 [DBG] state.OrderStatus: 4, ID: 55
2018-11-26 10:00:42.313 +00:00 [DBG] state.OrderStatus: 5, ID: 37
2018-11-26 10:00:42.313 +00:00 [DBG] state.OrderStatus: 6, ID: 80
2018-11-26 10:00:43.313 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 11
2018-11-26 10:00:44.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 20
2018-11-26 10:00:44.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 12
2018-11-26 10:00:45.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 60
2018-11-26 10:00:45.310 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 13
2018-11-26 10:00:45.312 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 21
2018-11-26 10:00:45.312 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 19
2018-11-26 10:00:45.318 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 82
2018-11-26 10:00:45.320 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 39
2018-11-26 10:00:46.313 +00:00 [DBG] state.OrderStatus: 3, ID: 46
2018-11-26 10:00:46.313 +00:00 [DBG] state.OrderStatus: 4, ID: 64
2018-11-26 10:00:46.313 +00:00 [DBG] state.OrderStatus: 3, ID: 69
2018-11-26 10:00:46.314 +00:00 [DBG] state.OrderStatus: 5, ID: 22
2018-11-26 10:00:46.315 +00:00 [DBG] state.OrderStatus: 6, ID: 71
2018-11-26 10:00:46.315 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 61
2018-11-26 10:00:46.315 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 24
2018-11-26 10:00:46.315 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 44
2018-11-26 10:00:46.317 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 15
2018-11-26 10:00:46.321 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 85
2018-11-26 10:00:46.324 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 62
2018-11-26 10:00:46.324 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 87
2018-11-26 10:00:46.325 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 45
2018-11-26 10:00:46.327 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 16
2018-11-26 10:00:46.331 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 88
2018-11-26 10:00:46.331 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 65
2018-11-26 10:00:46.333 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 83
2018-11-26 10:00:46.336 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 40
2018-11-26 10:00:46.338 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 86
2018-11-26 10:00:46.338 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 23
2018-11-26 10:00:46.343 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 66
2018-11-26 10:00:46.345 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 63
2018-11-26 10:00:46.345 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 89
2018-11-26 10:00:46.345 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 17
2018-11-26 10:00:46.351 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 41
2018-11-26 10:00:46.352 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 67
2018-11-26 10:00:46.353 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 90
2018-11-26 10:00:46.355 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 42
2018-11-26 10:00:46.359 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 43
2018-11-26 10:00:46.359 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 68
2018-11-26 10:00:46.362 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 91
2018-11-26 10:00:46.365 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 92
2018-11-26 10:00:46.368 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 93
2018-11-26 10:00:46.371 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 94
2018-11-26 10:00:46.374 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 95
2018-11-26 10:00:46.377 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 96
2018-11-26 10:00:46.380 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 97
2018-11-26 10:00:46.383 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 98
2018-11-26 10:00:46.386 +00:00 [DBG] INVALID - state.OrderStatus: 6, ID: 99
2018-11-26 10:00:47.321 +00:00 [DBG] state.OrderStatus: 1, ID: 38
2018-11-26 10:00:47.321 +00:00 [DBG] state.OrderStatus: 1, ID: 32
2018-11-26 10:00:51.323 +00:00 [DBG] state.OrderStatus: -1, ID: 47
2018-11-26 10:00:51.323 +00:00 [DBG] state.OrderStatus: -2, ID: 72
2018-11-26 10:00:51.324 +00:00 [DBG] state.OrderStatus: 0, ID: 70
2018-11-26 10:00:52.326 +00:00 [DBG] state.OrderStatus: 0, ID: 33
2018-11-26 10:00:56.327 +00:00 [DBG] state.OrderStatus: -1, ID: 73
2018-11-26 10:00:56.331 +00:00 [DBG] state.OrderStatus: 0, ID: 48
2018-11-26 10:01:01.331 +00:00 [DBG] state.OrderStatus: -1, ID: 74
2018-11-26 10:01:01.335 +00:00 [DBG] state.OrderStatus: -1, ID: 49
The state.OrderStatus value is getting to '6', for success and INVALID, and also it gets to negative in the log, and always not consistent.
Please advise what I'm doing wrong.
Upvotes: 1
Views: 32
Reputation: 156968
This is just a concurrency problem. The if (state.OrderStatus > 5)
can be evaluated by multiple threads to false, after which they all increment using state.OrderStatus++;
(which has a read-write issue on its own).
The solution is to use Interlocked.Increment
for such screnarios (note that state.OrderStatus
should be a field in order for this code to compile, not a property):
int current = Interlocked.Increment(ref state.OrderStatus);
try
{
if (current > 5)
{
Log.Debug("INVALID - state.OrderStatus: " + state.OrderStatus.ToString() + ", ID: " + id.ToString());
return await Task.FromResult("INVALID");
}
else
{
Log.Debug("state.OrderStatus: " + state.OrderStatus.ToString() + ", ID: " + id.ToString());
result = DoGetOrder(id);
}
}
finally
{
Interlocked.Decrement(ref state.OrderStatus);
}
Upvotes: 1