public void When_Composing_ToCachedString_CreateFromCachedString_Is_Identity(double rssi)
        {
            // arrange
            const string gatewayId = "foo";
            var          initial   = new PreferredGatewayTableItem(gatewayId, rssi);

            // act
            var result = PreferredGatewayTableItem.CreateFromCachedString(initial.ToCachedString());

            // assert
            Assert.Equal(initial.GatewayID, result.GatewayID);
            Assert.Equal(initial.Rssi, result.Rssi);
        }
Esempio n. 2
0
        private async Task <PreferredGatewayResult> ComputePreferredGateway(IPipelineExecutionContext context)
        {
            var computationId = Guid.NewGuid().ToString();
            var fcntUp        = context.Request.ClientFCntUp;
            var devEUI        = context.DevEUI;
            var rssi          = context.Request.Rssi.Value;

            // 1. Add request to list `preferred_gateway:deviceID:fcnt`, timeout: 5min
            //    List item: gatewayid, rssi, insertTime
            var item         = new PreferredGatewayTableItem(context.Request.GatewayId, rssi);
            var listCacheKey = LoRaDevicePreferredGateway.PreferredGatewayFcntUpItemListCacheKey(devEUI, fcntUp);

            this.cacheStore.ListAdd(listCacheKey, item.ToCachedString(), TimeSpan.FromMinutes(REQUEST_LIST_CACHE_DURATION_IN_MINUTES));
            this.log.LogInformation("Preferred gateway {devEUI}/{fcnt}: added {gateway} with {rssi}", devEUI, fcntUp, context.Request.GatewayId, rssi);

            // 2. Wait for the time specified in receiveInterval (default 200ms). Optional: wait less if another requests already started
            await Task.Delay(this.receiveInterval);

            // 3. Check if value was already calculated
            var preferredGateway = LoRaDevicePreferredGateway.LoadFromCache(this.cacheStore, devEUI);

            if (preferredGateway != null)
            {
                if (preferredGateway.FcntUp >= fcntUp)
                {
                    return(new PreferredGatewayResult(devEUI, fcntUp, preferredGateway));
                }
            }

            // 4. To calculated need to adquire a lock
            var preferredGatewayLockKey = $"preferredGateway:{devEUI}:lock";

            for (var i = 0; i < MAX_ATTEMPTS_TO_RESOLVE_PREFERRED_GATEWAY; i++)
            {
                if (await this.cacheStore.LockTakeAsync(preferredGatewayLockKey, computationId, TimeSpan.FromMilliseconds(200), block: false))
                {
                    try
                    {
                        preferredGateway = LoRaDevicePreferredGateway.LoadFromCache(this.cacheStore, devEUI);
                        if (preferredGateway == null || preferredGateway.FcntUp < fcntUp)
                        {
                            var items = this.cacheStore.ListGet(listCacheKey).Select(x => PreferredGatewayTableItem.CreateFromCachedString(x));

                            // if no table item was found (redis restarted, or delayed processing)?
                            // Return error, we don't want to save a value for each gateway or overwrite with a delayed request
                            var winner = items?.OrderByDescending(x => x.Rssi).FirstOrDefault();
                            if (winner == null)
                            {
                                this.log.LogError("Could not resolve closest gateway in {devEUI} and {fcntUp}", devEUI, fcntUp);

                                return(new PreferredGatewayResult(devEUI, fcntUp, "Could not resolve closest gateway"));
                            }

                            preferredGateway = new LoRaDevicePreferredGateway(winner.GatewayID, fcntUp);
                            LoRaDevicePreferredGateway.SaveToCache(this.cacheStore, context.DevEUI, preferredGateway);
                            this.log.LogInformation("Resolved preferred gateway {devEUI}/{fcnt}: {gateway} with {rssi}", devEUI, fcntUp, context.Request.GatewayId, rssi);
                        }
                    }
                    finally
                    {
                        this.cacheStore.LockRelease(preferredGatewayLockKey, computationId);
                    }
                }
                else
                {
                    // We couldn't get lock
                    // wait a bit and try to get result
                    await Task.Delay(Math.Max(50, this.receiveInterval / 4));

                    preferredGateway = LoRaDevicePreferredGateway.LoadFromCache(this.cacheStore, context.DevEUI);
                }

                if (preferredGateway != null)
                {
                    if (preferredGateway.FcntUp >= fcntUp)
                    {
                        return(new PreferredGatewayResult(devEUI, fcntUp, preferredGateway));
                    }
                }
            }

            this.log.LogError("Could not resolve closest gateway in {devEUI} and {fcntUp}", devEUI, fcntUp);
            return(new PreferredGatewayResult(devEUI, fcntUp, "Could not resolve closest gateway"));
        }