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); }
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")); }