FdcResourceArbitrator.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Edge.Core.Database;
  8. using Edge.Core.Database.Models;
  9. using Microsoft.EntityFrameworkCore;
  10. using Microsoft.Extensions.DependencyInjection;
  11. using Microsoft.Extensions.Logging;
  12. namespace Applications.FDC
  13. {
  14. public class FdcResourceArbitrator : IFdcResourceArbitrator
  15. {
  16. public static ILogger fdcLogger { get; set; }// = ServiceBuilder.Provider.GetRequiredService<ILoggerFactory>().CreateLogger("FdcServer");// NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("FdcServer");
  17. private static FdcResourceArbitrator instance = new FdcResourceArbitrator();
  18. private object syncObject = new object();
  19. public static FdcResourceArbitrator Default => instance;
  20. //private SqliteDbContext dbContext;
  21. private FdcResourceArbitrator()
  22. {
  23. }
  24. private int __TryReserveFuelPointAsyncGuard = 0;
  25. public async Task<bool> TryReserveFuelPointAsync(int posId, int fuelPointId)
  26. {
  27. if (0 != Interlocked.CompareExchange(ref this.__TryReserveFuelPointAsyncGuard, 1, 0))
  28. return false;
  29. try
  30. {
  31. var dbContext = new SqliteDbContext();
  32. var existed = dbContext.FuelPointReservationModels.Where(f => f.FuelPointId == fuelPointId);
  33. if (!existed.Any())
  34. {
  35. dbContext.FuelPointReservationModels.Add(new FuelPointReservation()
  36. {
  37. FuelPointId = fuelPointId,
  38. ReservingTime = DateTime.Now,
  39. ReservedByFdcClientId = posId
  40. });
  41. await dbContext.SaveChangesAsync();
  42. fdcLogger.LogDebug(" created reservation");
  43. return true;
  44. }
  45. // add a protection for deadlock by someone, if locked by lasted over 5m, auto unlocked it.
  46. int maxHoldTime = 5;
  47. if (existed.Any())
  48. {
  49. var lockDue = DateTime.Now.Subtract(existed.First().ReservingTime);
  50. if (lockDue >= new TimeSpan(0, maxHoldTime, 0))
  51. {
  52. fdcLogger.LogInformation(" FuelPoint with id: " + fuelPointId + " have been hold by "
  53. + existed.First().ReservedByFdcClientId
  54. + " for " + lockDue.TotalMinutes + " minutes (since: "
  55. + existed.First().ReservingTime.ToString("yyyy-MM-dd HH:mm:ss") + "), will auto unlock it for avoid long lock");
  56. dbContext.FuelPointReservationModels.Remove(existed.First());
  57. await dbContext.SaveChangesAsync();
  58. existed = dbContext.FuelPointReservationModels.Where(f => f.FuelPointId == fuelPointId);
  59. }
  60. }
  61. if (!existed.Any())
  62. {
  63. dbContext.FuelPointReservationModels.Add(new FuelPointReservation()
  64. {
  65. FuelPointId = fuelPointId,
  66. ReservingTime = DateTime.Now,
  67. ReservedByFdcClientId = posId
  68. });
  69. await dbContext.SaveChangesAsync();
  70. fdcLogger.LogDebug(" Created a FuelPoint Reservation for FdcClientId: " + posId);
  71. return true;
  72. }
  73. else
  74. {
  75. if (existed.First().ReservedByFdcClientId != posId)
  76. {
  77. fdcLogger.LogDebug(" ReserveFuelPoint failed due to Fp already locked by FdcClientId: " + existed.First().ReservedByFdcClientId);
  78. return false;
  79. }
  80. else
  81. {
  82. // refresh the time
  83. existed.First().ReservingTime = DateTime.Now;
  84. await dbContext.SaveChangesAsync();
  85. fdcLogger.LogDebug(" Updated FuelPoint ReservingTime since previous already reserved by caller FdcClientId: " + posId);
  86. return true;
  87. }
  88. }
  89. }
  90. catch (Exception exxx)
  91. {
  92. fdcLogger.LogError("TryReserveFuelPoint exceptioned: " + exxx.ToString());
  93. return false;
  94. }
  95. finally
  96. {
  97. this.__TryReserveFuelPointAsyncGuard = 0;
  98. }
  99. }
  100. private int __TryUnreserveFuelPointAsyncGuard = 0;
  101. public async Task<bool> TryUnreserveFuelPointAsync(int posId, int fuelPointId)
  102. {
  103. if (0 != Interlocked.CompareExchange(ref this.__TryUnreserveFuelPointAsyncGuard, 1, 0))
  104. return false;
  105. try
  106. {
  107. var dbContext = new SqliteDbContext();
  108. var reservedBySelf = dbContext.FuelPointReservationModels.Where(f => f.FuelPointId == fuelPointId
  109. && f.ReservedByFdcClientId == posId);
  110. if (!reservedBySelf.Any())
  111. {
  112. fdcLogger.LogError(" failed on unreserve target pump with pump id:" + fuelPointId
  113. + " since it didn't get reserved or others already reserved it");
  114. return false;
  115. }
  116. else
  117. {
  118. dbContext.FuelPointReservationModels.Remove(reservedBySelf.First());
  119. await dbContext.SaveChangesAsync();
  120. return true;
  121. }
  122. }
  123. catch (Exception exxx)
  124. {
  125. fdcLogger.LogError("TryUnreserveFuelPoint exceptioned: " + exxx.ToString());
  126. return false;
  127. }
  128. finally
  129. {
  130. this.__TryUnreserveFuelPointAsyncGuard = 0;
  131. }
  132. }
  133. private int __TryLockFuelSaleTrxAsyncGuard = 0;
  134. public async Task<FuelSaleTransaction> TryLockFuelSaleTrxAsync(int posId, int fuelPointId, int transactionNo, int trxDbUniqueId)
  135. {
  136. if (0 != Interlocked.CompareExchange(ref this.__TryLockFuelSaleTrxAsyncGuard, 1, 0))
  137. return null;
  138. try
  139. {
  140. var dbContext = new SqliteDbContext();
  141. var targetTrx = dbContext.PumpTransactionModels.Where(f =>
  142. f.ReleaseToken == trxDbUniqueId
  143. && f.PumpId == fuelPointId
  144. && f.TransactionSeqNumberFromPhysicalPump == transactionNo.ToString()
  145. && f.State == FuelSaleTransactionState.Payable).OrderByDescending(t => t.SaleStartTime).FirstOrDefault();
  146. byte maxRetryTimes = 3;
  147. byte retriedTimes = 0;
  148. retry:
  149. if (targetTrx != null)
  150. {
  151. if (string.IsNullOrEmpty(targetTrx.LockedByFdcClientId))
  152. {
  153. targetTrx.LockedTime = DateTime.Now;
  154. targetTrx.LockedByFdcClientId = posId.ToString();
  155. targetTrx.State = FuelSaleTransactionState.Locked;
  156. try
  157. {
  158. await dbContext.SaveChangesAsync();
  159. }
  160. catch (DbUpdateConcurrencyException ex)
  161. {
  162. fdcLogger.LogInformation(" lock FuelSaleTrx failed due to DbUpdateConcurrencyException, targetTrx in app memory is: "
  163. + targetTrx.ToString());
  164. try
  165. {
  166. // Update the values of the entity that failed to save from the store
  167. ex.Entries.Single().Reload();
  168. fdcLogger.LogInformation(" while the actual in db is: "
  169. + targetTrx.ToString()
  170. + System.Environment.NewLine + "will retry saving...");
  171. retriedTimes++;
  172. if (retriedTimes > maxRetryTimes)
  173. { fdcLogger.LogInformation(" DbUpdateConcurrency retried time reached its max, will quit."); return null; }
  174. goto retry;
  175. }
  176. catch (Exception exxx)
  177. {
  178. fdcLogger.LogInformation(" exceptioned again for reload targetTrx from db: " + exxx);
  179. return null;
  180. }
  181. }
  182. return targetTrx;
  183. }
  184. else if (targetTrx.LockedByFdcClientId == posId.ToString())
  185. {
  186. fdcLogger.LogDebug(" lock FuelSaleTrx succeed since POS: " + posId + " already own it");
  187. return targetTrx;
  188. }
  189. else
  190. {
  191. fdcLogger.LogInformation(" lock FuelSaleTrx failed due to it already locked by other POS: "
  192. + targetTrx.LockedByFdcClientId + " at "
  193. + (targetTrx.LockedTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""));
  194. return null;
  195. }
  196. }
  197. else
  198. {
  199. fdcLogger.LogInformation(" lock FuelSaleTrx failed since it not existed or state is not Payable");
  200. return null;
  201. }
  202. }
  203. catch (Exception exxx)
  204. {
  205. fdcLogger.LogError("TryLockFuelSaleTrx exceptioned: " + exxx.ToString());
  206. return null;
  207. }
  208. finally { this.__TryLockFuelSaleTrxAsyncGuard = 0; }
  209. }
  210. private int __TryUnlockFuelSaleTrxAsyncGuard = 0;
  211. /// <summary>
  212. /// Try set the SaleTrx state to Payable.
  213. /// </summary>
  214. /// <param name="posId"></param>
  215. /// <param name="fuelPointId"></param>
  216. /// <param name="transactionNo"></param>
  217. /// <param name="trxDbUniqueId"></param>
  218. /// <returns></returns>
  219. public async Task<FuelSaleTransaction> TryUnlockFuelSaleTrxAsync(int posId, int fuelPointId, int transactionNo, int trxDbUniqueId)
  220. {
  221. if (0 != Interlocked.CompareExchange(ref this.__TryUnlockFuelSaleTrxAsyncGuard, 1, 0))
  222. return null;
  223. try
  224. {
  225. var dbContext = new SqliteDbContext();
  226. var targetTrx = dbContext.PumpTransactionModels.Where(f =>
  227. f.ReleaseToken == trxDbUniqueId
  228. && f.PumpId == fuelPointId
  229. && f.TransactionSeqNumberFromPhysicalPump == transactionNo.ToString()
  230. && f.State == FuelSaleTransactionState.Locked
  231. && f.LockedByFdcClientId == posId.ToString()).FirstOrDefault();
  232. if (targetTrx != null)
  233. {
  234. fdcLogger.LogDebug(" unlock FuelSaleTrx succeed");
  235. targetTrx.LockedByFdcClientId = "";
  236. targetTrx.LockedTime = DateTime.MinValue;
  237. targetTrx.State = FuelSaleTransactionState.Payable;
  238. await dbContext.SaveChangesAsync();
  239. return targetTrx;
  240. }
  241. else
  242. {
  243. fdcLogger.LogInformation(" unlock FuelSaleTrx failed");
  244. return null;
  245. }
  246. }
  247. catch (Exception exxx)
  248. {
  249. fdcLogger.LogError("TryUnlockFuelSaleTrx exceptioned: " + exxx.ToString());
  250. return null;
  251. }
  252. finally { this.__TryUnlockFuelSaleTrxAsyncGuard = 0; }
  253. }
  254. //private int __TryLockNozzleAsyncGuard = 0;
  255. ///// <summary>
  256. /////
  257. ///// </summary>
  258. ///// <param name="posId"></param>
  259. ///// <param name="fuelPointId"></param>
  260. ///// <param name="logicalNozzleId"></param>
  261. ///// <returns></returns>
  262. //public async Task<bool> TryLockNozzleAsync(int posId, int fuelPointId, int logicalNozzleId)
  263. //{
  264. // if (0 != Interlocked.CompareExchange(ref this.__TryLockNozzleAsyncGuard, 1, 0))
  265. // return false;
  266. // try
  267. // {
  268. // var dbContext = new SqliteDbContext();
  269. // var existsLockingNozzleRecord = dbContext.NozzleLocks.Where(f =>
  270. // f.FuelPointId == fuelPointId
  271. // && f.LogicalNozzleId == logicalNozzleId).FirstOrDefault();
  272. // if (existsLockingNozzleRecord != null)
  273. // {
  274. // if (existsLockingNozzleRecord.LockedByFdcClientId != null
  275. // && existsLockingNozzleRecord.LockedByFdcClientId == posId)
  276. // {
  277. // existsLockingNozzleRecord.LockingTime = DateTime.Now;
  278. // try
  279. // {
  280. // await dbContext.SaveChangesAsync();
  281. // return true;
  282. // }
  283. // catch (Exception ex)
  284. // {
  285. // fdcLogger.LogInformation(" exceptioned for updating locking time for lock nozzle from db: " + ex);
  286. // return false;
  287. // }
  288. // }
  289. // else if (existsLockingNozzleRecord.LockedByFdcClientId != null
  290. // && existsLockingNozzleRecord.LockedByFdcClientId != posId)
  291. // {
  292. // fdcLogger.LogInformation(" lock nozzle failed due to already locked by POS: " + existsLockingNozzleRecord.LockedByFdcClientId);
  293. // return false;
  294. // }
  295. // }
  296. // try
  297. // {
  298. // var newNozzleLocking = new NozzleLock()
  299. // {
  300. // LockingTime = DateTime.Now,
  301. // LockedByFdcClientId = posId,
  302. // FuelPointId = fuelPointId,
  303. // LogicalNozzleId = logicalNozzleId
  304. // };
  305. // dbContext.NozzleLocks.Add(newNozzleLocking);
  306. // await dbContext.SaveChangesAsync();
  307. // return true;
  308. // }
  309. // catch (Exception ex)
  310. // {
  311. // fdcLogger.LogInformation(" exceptioned for create a lock nozzle from db: " + ex);
  312. // return false;
  313. // }
  314. // }
  315. // catch { return false; }
  316. // finally { this.__TryLockNozzleAsyncGuard = 0; }
  317. //}
  318. //private int __TryUnlockNozzleAsyncGuard = 0;
  319. ///// <summary>
  320. /////
  321. ///// </summary>
  322. ///// <param name="posId"></param>
  323. ///// <param name="fuelPointId"></param>
  324. ///// <param name="logicalNozzleId"></param>
  325. ///// <returns></returns>
  326. //public async Task<NozzleLock> TryUnlockNozzleAsync(int posId, int fuelPointId, int logicalNozzleId)
  327. //{
  328. // if (0 != Interlocked.CompareExchange(ref this.__TryUnlockNozzleAsyncGuard, 1, 0))
  329. // return null;
  330. // try
  331. // {
  332. // var dbContext = new SqliteDbContext();
  333. // var targetTrx = dbContext.NozzleLocks.Where(f =>
  334. // f.FuelPointId == fuelPointId
  335. // && f.LogicalNozzleId == logicalNozzleId
  336. // && f.LockedByFdcClientId == posId).FirstOrDefault();
  337. // if (targetTrx != null)
  338. // {
  339. // fdcLogger.LogDebug(" unlock NozzleAsync succeed");
  340. // targetTrx.LockedByFdcClientId = null;
  341. // targetTrx.LockingTime = null;
  342. // await dbContext.SaveChangesAsync();
  343. // return targetTrx;
  344. // }
  345. // else
  346. // {
  347. // fdcLogger.LogInformation(" unlock NozzleAsync failed");
  348. // return null;
  349. // }
  350. // }
  351. // catch (Exception exxx)
  352. // {
  353. // fdcLogger.LogError("TryUnlockNozzleAsync exceptioned: " + exxx.ToString());
  354. // return null;
  355. // }
  356. // finally { this.__TryUnlockNozzleAsyncGuard = 0; }
  357. //}
  358. }
  359. }