using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Edge.Core.Database; using Edge.Core.Database.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Applications.FDC { public class FdcResourceArbitrator : IFdcResourceArbitrator { public static ILogger fdcLogger { get; set; }// = ServiceBuilder.Provider.GetRequiredService().CreateLogger("FdcServer");// NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("FdcServer"); private static FdcResourceArbitrator instance = new FdcResourceArbitrator(); private object syncObject = new object(); public static FdcResourceArbitrator Default => instance; //private SqliteDbContext dbContext; private FdcResourceArbitrator() { } private int __TryReserveFuelPointAsyncGuard = 0; public async Task TryReserveFuelPointAsync(int posId, int fuelPointId) { if (0 != Interlocked.CompareExchange(ref this.__TryReserveFuelPointAsyncGuard, 1, 0)) return false; try { var dbContext = new SqliteDbContext(); var existed = dbContext.FuelPointReservationModels.Where(f => f.FuelPointId == fuelPointId); if (!existed.Any()) { dbContext.FuelPointReservationModels.Add(new FuelPointReservation() { FuelPointId = fuelPointId, ReservingTime = DateTime.Now, ReservedByFdcClientId = posId }); await dbContext.SaveChangesAsync(); fdcLogger.LogDebug(" created reservation"); return true; } // add a protection for deadlock by someone, if locked by lasted over 5m, auto unlocked it. int maxHoldTime = 5; if (existed.Any()) { var lockDue = DateTime.Now.Subtract(existed.First().ReservingTime); if (lockDue >= new TimeSpan(0, maxHoldTime, 0)) { fdcLogger.LogInformation(" FuelPoint with id: " + fuelPointId + " have been hold by " + existed.First().ReservedByFdcClientId + " for " + lockDue.TotalMinutes + " minutes (since: " + existed.First().ReservingTime.ToString("yyyy-MM-dd HH:mm:ss") + "), will auto unlock it for avoid long lock"); dbContext.FuelPointReservationModels.Remove(existed.First()); await dbContext.SaveChangesAsync(); existed = dbContext.FuelPointReservationModels.Where(f => f.FuelPointId == fuelPointId); } } if (!existed.Any()) { dbContext.FuelPointReservationModels.Add(new FuelPointReservation() { FuelPointId = fuelPointId, ReservingTime = DateTime.Now, ReservedByFdcClientId = posId }); await dbContext.SaveChangesAsync(); fdcLogger.LogDebug(" Created a FuelPoint Reservation for FdcClientId: " + posId); return true; } else { if (existed.First().ReservedByFdcClientId != posId) { fdcLogger.LogDebug(" ReserveFuelPoint failed due to Fp already locked by FdcClientId: " + existed.First().ReservedByFdcClientId); return false; } else { // refresh the time existed.First().ReservingTime = DateTime.Now; await dbContext.SaveChangesAsync(); fdcLogger.LogDebug(" Updated FuelPoint ReservingTime since previous already reserved by caller FdcClientId: " + posId); return true; } } } catch (Exception exxx) { fdcLogger.LogError("TryReserveFuelPoint exceptioned: " + exxx.ToString()); return false; } finally { this.__TryReserveFuelPointAsyncGuard = 0; } } private int __TryUnreserveFuelPointAsyncGuard = 0; public async Task TryUnreserveFuelPointAsync(int posId, int fuelPointId) { if (0 != Interlocked.CompareExchange(ref this.__TryUnreserveFuelPointAsyncGuard, 1, 0)) return false; try { var dbContext = new SqliteDbContext(); var reservedBySelf = dbContext.FuelPointReservationModels.Where(f => f.FuelPointId == fuelPointId && f.ReservedByFdcClientId == posId); if (!reservedBySelf.Any()) { fdcLogger.LogError(" failed on unreserve target pump with pump id:" + fuelPointId + " since it didn't get reserved or others already reserved it"); return false; } else { dbContext.FuelPointReservationModels.Remove(reservedBySelf.First()); await dbContext.SaveChangesAsync(); return true; } } catch (Exception exxx) { fdcLogger.LogError("TryUnreserveFuelPoint exceptioned: " + exxx.ToString()); return false; } finally { this.__TryUnreserveFuelPointAsyncGuard = 0; } } private int __TryLockFuelSaleTrxAsyncGuard = 0; public async Task TryLockFuelSaleTrxAsync(int posId, int fuelPointId, int transactionNo, int trxDbUniqueId) { if (0 != Interlocked.CompareExchange(ref this.__TryLockFuelSaleTrxAsyncGuard, 1, 0)) return null; try { var dbContext = new SqliteDbContext(); var targetTrx = dbContext.PumpTransactionModels.Where(f => f.ReleaseToken == trxDbUniqueId && f.PumpId == fuelPointId && f.TransactionSeqNumberFromPhysicalPump == transactionNo.ToString() && f.State == FuelSaleTransactionState.Payable).OrderByDescending(t => t.SaleStartTime).FirstOrDefault(); byte maxRetryTimes = 3; byte retriedTimes = 0; retry: if (targetTrx != null) { if (string.IsNullOrEmpty(targetTrx.LockedByFdcClientId)) { targetTrx.LockedTime = DateTime.Now; targetTrx.LockedByFdcClientId = posId.ToString(); targetTrx.State = FuelSaleTransactionState.Locked; try { await dbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { fdcLogger.LogInformation(" lock FuelSaleTrx failed due to DbUpdateConcurrencyException, targetTrx in app memory is: " + targetTrx.ToString()); try { // Update the values of the entity that failed to save from the store ex.Entries.Single().Reload(); fdcLogger.LogInformation(" while the actual in db is: " + targetTrx.ToString() + System.Environment.NewLine + "will retry saving..."); retriedTimes++; if (retriedTimes > maxRetryTimes) { fdcLogger.LogInformation(" DbUpdateConcurrency retried time reached its max, will quit."); return null; } goto retry; } catch (Exception exxx) { fdcLogger.LogInformation(" exceptioned again for reload targetTrx from db: " + exxx); return null; } } return targetTrx; } else if (targetTrx.LockedByFdcClientId == posId.ToString()) { fdcLogger.LogDebug(" lock FuelSaleTrx succeed since POS: " + posId + " already own it"); return targetTrx; } else { fdcLogger.LogInformation(" lock FuelSaleTrx failed due to it already locked by other POS: " + targetTrx.LockedByFdcClientId + " at " + (targetTrx.LockedTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "")); return null; } } else { fdcLogger.LogInformation(" lock FuelSaleTrx failed since it not existed or state is not Payable"); return null; } } catch (Exception exxx) { fdcLogger.LogError("TryLockFuelSaleTrx exceptioned: " + exxx.ToString()); return null; } finally { this.__TryLockFuelSaleTrxAsyncGuard = 0; } } private int __TryUnlockFuelSaleTrxAsyncGuard = 0; /// /// Try set the SaleTrx state to Payable. /// /// /// /// /// /// public async Task TryUnlockFuelSaleTrxAsync(int posId, int fuelPointId, int transactionNo, int trxDbUniqueId) { if (0 != Interlocked.CompareExchange(ref this.__TryUnlockFuelSaleTrxAsyncGuard, 1, 0)) return null; try { var dbContext = new SqliteDbContext(); var targetTrx = dbContext.PumpTransactionModels.Where(f => f.ReleaseToken == trxDbUniqueId && f.PumpId == fuelPointId && f.TransactionSeqNumberFromPhysicalPump == transactionNo.ToString() && f.State == FuelSaleTransactionState.Locked && f.LockedByFdcClientId == posId.ToString()).FirstOrDefault(); if (targetTrx != null) { fdcLogger.LogDebug(" unlock FuelSaleTrx succeed"); targetTrx.LockedByFdcClientId = ""; targetTrx.LockedTime = DateTime.MinValue; targetTrx.State = FuelSaleTransactionState.Payable; await dbContext.SaveChangesAsync(); return targetTrx; } else { fdcLogger.LogInformation(" unlock FuelSaleTrx failed"); return null; } } catch (Exception exxx) { fdcLogger.LogError("TryUnlockFuelSaleTrx exceptioned: " + exxx.ToString()); return null; } finally { this.__TryUnlockFuelSaleTrxAsyncGuard = 0; } } //private int __TryLockNozzleAsyncGuard = 0; ///// ///// ///// ///// ///// ///// ///// //public async Task TryLockNozzleAsync(int posId, int fuelPointId, int logicalNozzleId) //{ // if (0 != Interlocked.CompareExchange(ref this.__TryLockNozzleAsyncGuard, 1, 0)) // return false; // try // { // var dbContext = new SqliteDbContext(); // var existsLockingNozzleRecord = dbContext.NozzleLocks.Where(f => // f.FuelPointId == fuelPointId // && f.LogicalNozzleId == logicalNozzleId).FirstOrDefault(); // if (existsLockingNozzleRecord != null) // { // if (existsLockingNozzleRecord.LockedByFdcClientId != null // && existsLockingNozzleRecord.LockedByFdcClientId == posId) // { // existsLockingNozzleRecord.LockingTime = DateTime.Now; // try // { // await dbContext.SaveChangesAsync(); // return true; // } // catch (Exception ex) // { // fdcLogger.LogInformation(" exceptioned for updating locking time for lock nozzle from db: " + ex); // return false; // } // } // else if (existsLockingNozzleRecord.LockedByFdcClientId != null // && existsLockingNozzleRecord.LockedByFdcClientId != posId) // { // fdcLogger.LogInformation(" lock nozzle failed due to already locked by POS: " + existsLockingNozzleRecord.LockedByFdcClientId); // return false; // } // } // try // { // var newNozzleLocking = new NozzleLock() // { // LockingTime = DateTime.Now, // LockedByFdcClientId = posId, // FuelPointId = fuelPointId, // LogicalNozzleId = logicalNozzleId // }; // dbContext.NozzleLocks.Add(newNozzleLocking); // await dbContext.SaveChangesAsync(); // return true; // } // catch (Exception ex) // { // fdcLogger.LogInformation(" exceptioned for create a lock nozzle from db: " + ex); // return false; // } // } // catch { return false; } // finally { this.__TryLockNozzleAsyncGuard = 0; } //} //private int __TryUnlockNozzleAsyncGuard = 0; ///// ///// ///// ///// ///// ///// ///// //public async Task TryUnlockNozzleAsync(int posId, int fuelPointId, int logicalNozzleId) //{ // if (0 != Interlocked.CompareExchange(ref this.__TryUnlockNozzleAsyncGuard, 1, 0)) // return null; // try // { // var dbContext = new SqliteDbContext(); // var targetTrx = dbContext.NozzleLocks.Where(f => // f.FuelPointId == fuelPointId // && f.LogicalNozzleId == logicalNozzleId // && f.LockedByFdcClientId == posId).FirstOrDefault(); // if (targetTrx != null) // { // fdcLogger.LogDebug(" unlock NozzleAsync succeed"); // targetTrx.LockedByFdcClientId = null; // targetTrx.LockingTime = null; // await dbContext.SaveChangesAsync(); // return targetTrx; // } // else // { // fdcLogger.LogInformation(" unlock NozzleAsync failed"); // return null; // } // } // catch (Exception exxx) // { // fdcLogger.LogError("TryUnlockNozzleAsync exceptioned: " + exxx.ToString()); // return null; // } // finally { this.__TryUnlockNozzleAsyncGuard = 0; } //} } }