Aby
Aby

Reputation: 1924

ASP.NET Singleton class values not consistent

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

Answers (1)

Patrick Hofman
Patrick Hofman

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

Related Questions