.NET 5 WebAPI - zablokowanie endpointa, aż nie zakończy jednego zadania

0

Witam.
Mam pewien problem z aplikacją mobilną, która wysyła dokumenty do API w tle. Działa to różnie, ze względu na to jak działa Android (nie ma aplikacji na iOS). Oczywiście mam porobiony ify czy dokument już istnieje ale niestety dostaje zapytania do API w tej samej milisekundzie, co jest dziwne i niemożliwe do "zaifowania".

LOGI Z API

2021-10-11 11:32:53.1806|0|INFO|PanelOptimo.Controllers.OptimoController|[INSERTING_OBJECT] {"action":0,"customer":60061,"description":"","magazyn":1,"offlineNumber":"ZAM/375/10/2021/B22PT ","products":[]}
2021-10-11 11:32:53.1806|0|INFO|PanelOptimo.Controllers.OptimoController|[INSERTING_OBJECT] {"action":0,"customer":60061,"description":"","magazyn":1,"offlineNumber":"ZAM/375/10/2021/B22PT ","products":[]}
2021-10-11 11:32:53.1806|0|INFO|PanelOptimo.Controllers.OptimoController|[INSERTING_OBJECT] {"action":0,"customer":60061,"description":"","magazyn":1,"offlineNumber":"ZAM/375/10/2021/B22PT ","products":[]}

API zalogowało 3 razy ten sam dokument w tej samej milisekundzie. Jak to w ogóle jest możliwe? Jak zapobiec takiemu zachowaniu? Po stronie mobilki już wrzuciłem ifa na timestamp z różnicą na 10 sekund ale Android jest tak dziwny w przypadku headless events, że chyba prościej i pewniej będzie "zablokować" to po stronie API.

1

Żeby powiedzieć coś więcej o tym co się dzieje to musiałbyś udostępnić trochę kodu zarówno apki androidowej jak i może samego API (bo może to nie jest efekt tego że android strzela kilka razy jednocześnie a tego jak zaimplementowano logowanie).

Odnosząc się jednak do pytania - nie bawiłbym się w żadne blokowanie endpointów bo to nie ma sensu.
Po pierwsze kłóci się to trochę z samą ideą API, które powinno przyjmować tyle żądań ile jest do niego wysyłanych. Po drugie cała logika powinna siedzieć przecież w modelu. W samym modelu można więc wrzucać żądania do kolejki i wykonywać pojedynczo. Jako, że żądania są wykonywane w różnych wątkach można też wykorzystać prosty mechanizm synchronizacji np w postaci zwykłego locka czy innego bardziej wyszukanego rozwiązania jeśli jest potrzebne.

0

To jest stare API, które zostało wykorzystane do "nowszego" podejścia. Wcześniej nie było tego problemu, ponieważ aplikacja (PWA) była cały czas online, ponieważ było to www. W tym momencie duży nacisk został położony na pracę offline po stronie mobilki, stąd durne synchronizowanie dokumentów (zamówień) w tle. Obawiam się, że kod nic nie powie, ponieważ twierdzę, że to nie jest kwestia API, a tego jak działa Android i jego praca w tle. Przepisywanie API na "poprawne" też nie jest możliwe, ponieważ nie mam czasu, który mógłbym na to poświęcić i tak tylko dokładam więcej bałaganu to już istniejącego.

Logowanie jest zrobione za pomocą NLog
Startup.cs

LogManager.LoadConfiguration(string.Concat(Directory.GetCurrentDirectory(), "/nlog.config"));

Program.cs

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(LogLevel.Debug);
                }).UseNLog();

OptimoController.cs


        [HttpPost]
        public async Task<IActionResult> InsertNewDocument([FromBody] object parameter)
        {
// ...
                ClaimsIdentity claimsIdentity = User.Identity as ClaimsIdentity;
                SysUser u = JsonConvert.DeserializeObject<SysUser>(claimsIdentity.FindFirst("user").Value);
                SysUserConfig uu = JsonConvert.DeserializeObject<SysUserConfig>(claimsIdentity.FindFirst("config").Value);

                _loggerManager.LogInformation("[INSERTING_USER] " + u.Email);
                _loggerManager.LogInformation("[INSERTING_SERIE] " + uu.UserSerie);
                _loggerManager.LogInformation("[INSERTING_OBJECT] " + JsonConvert.SerializeObject(parameter));
// ...

                    await _optimoService.SaveTraNag(trn, db);

OptimoService.cs

public async Task SaveTraNag(TraNag trn, IDbConnection db)
{
     var existing = await _sqlService.QuerySingleCompany<int>("select count(*) from CDN.TraNag where TrN_NumerObcy = @Number", new { Number = trn.TrN_NumerObcy });
     if (existing == 0)
     {
     }
}

Tak, wiem, logiki nie powinno być w kontrolerze. Nie chce tego poprawiać i w najbliższej przyszłości nie mam zamiaru. Problem polega na tym, że API nie czeka z przerobieniem danych tylko przyjmuje kolejne, co powoduje duplikacje w bazie. Te same zamówienie wpada po kilka razy, ponieważ if(existing == 0) zwraca true, bo tego dokumentu faktycznie jeszcze tam nie ma.

MOBILKA - Flutter (Dart)

Future<void> initPlatformState() async {
  await BackgroundFetch.configure(
      BackgroundFetchConfig(
          requiresBatteryNotLow: false,
          requiresCharging: false,
          requiresDeviceIdle: false,
          requiresStorageNotLow: false,
          minimumFetchInterval: 5,
          startOnBoot: true,
          stopOnTerminate: false,
          enableHeadless: true,
          forceAlarmManager: true,
          requiredNetworkType: NetworkType.ANY), (String taskId) async {
    final date = Hive.box<String>(Methods.appSettingsBox).get(Methods.synchronizeDate);
    if (DateTime.now().millisecondsSinceEpoch - lastFetchEventAt < 10000) {
      BackgroundFetch.finish(taskId);
    } else {
      if (auth.isAuthenticated() && date != null) {
        MainScreen.docController.documentSynchronization();
        lastFetchEventAt = DateTime.now().millisecondsSinceEpoch;
      }
      BackgroundFetch.finish(taskId);
    }
  }, (String taskId) async {
    BackgroundFetch.finish(taskId);
  });
}

Temat był drążony z autorem biblioteki.
Posibility to execute same job in two different threads #248

0
AdamWox napisał(a):

Tak, wiem, logiki nie powinno być w kontrolerze. Nie chce tego poprawiać i w najbliższej przyszłości nie mam zamiaru. Problem polega na tym, że API nie czeka z przerobieniem danych tylko przyjmuje kolejne, co powoduje duplikacje w bazie.

Tyle, że to nie jest problem API bo WebAPI tak właśnie działa by design. Każdy request jest wykonywany w osobnym wątku i jeśli zaimplementujesz żadnego mechanizmu synchronizacji to wiele wątków będzie mogło dowolnie wchodzić w sekcję krytyczną i robić co im się tam podoba w dowolnej kolejności.

Trzeba więc w takim razie albo te żądania zakolejkować albo użyć locka (czy czegoś innego jak trzeba). Ale wpinając mechanizm synchronizacji powinno się pamiętać o tym, że jeśli oprócz zapisu gdzieś te dane są odczytywane to trzeba je również zabezpieczyć przed byciem odczytywanym w momencie, w którym inny wątek robi zapis. Z tym, że jeśli założysz tego locka w API to ryzykujesz tym, że jeśli będzie korzystać z niego wielu klientów to wydajność spadnie bo każde żądanie będzie musiało poczekać na wykonanie tych, które już czekają.

To wygląda na problem apki androidowej więc prawdę mówiąc szukałbym rozwiazania właśnie tam i to tam upewnił się, że żądania są wykonywane sekwencyjnie a nie jednocześnie.

0

No właśnie w apce androidowej zrobiłem już wszystko co mogłem. Gość mi zasugerował co poprawić, zrobiłem to i dalej są cyrki. Problem polega na tym, że system Android to nie Windows i rządzi się kompletnie innymi prawami w kwestii operacji w tle. Nie obchodzi mnie czy kogoś zablokuje z API, ponieważ wymiana danych przebiega tylko w tle. Kontrahenci, towary są synchronizowane raz i trzymane w cache + wstępna synchronizacja dokumentów (już zapisanych). Każde kolejne zamówienie jest dodawane w tle. Zauważyłem, że jeśli mobilka ma problem z wysłaniem przez dłuższy czas (error 500, z powodu błędów w ERP) to potem leci takim hurtem, że mam 8 dokumentów z tego samego zamówienia. To samo się dzieje w stanie bezczynności. Gość odłoży urządzenie na dłuższy czas, weźmie na chwilę, aby coś sprawdzić i znowu to samo. Tak jakby Android kolejkował każde uruchomienie background_workera i potem wszystkie wywołania uruchamiał na raz jak tylko wyjdzie ze stanu idle.

Urządzeniami są tablety, które internet mają po WIFI z telefonu. Przedstawiciel jedzie do klienta i zapisuje zamówienie na tablecie (cache). Jeśli klient potwierdza zamówienie (słownie), to przedstawiciel klika w opcje "Przekaż do realizacji" i taki dokument przy następnym interwale leci do API i robi się z niego dokument w systemie ERP (Comarch Optima).

Ten system jest bardzo "wadliwy" z powodu swojej pracy offline'owej ale taki był wymóg klienta, ponieważ jak trafi się przedstawicielowi klient w bunkrze to nie zrobi zamówienia.

Tak jak napisałem wcześniej. Nie interesuje mnie czy "zablokuje" API. Chce zablokować. Zapis dokumentu nie trwa dłużej niż 10 sekund, zamówienie nie musi wpaść od razu. Po prostu musi wpaść, najlepiej raz.

W przypadku lock niestety chyba nie mogę zastosować, ponieważ nie mogę wtedy korzystać z await, a to pierwsze mi do głowy przyszło.

0

async/await nie służy do synchronizacji wątków. Skoro te API nie jest wcale obciążone to może tego nawet nie potrzebujesz i możesz to zaorać i uzyć zwyczajnego locka. Jeśli uprzesz się na upieranie async/await to mozesz zabezpieczyć sekcję krytyczną semaforem ale moim zdaniem nie ma to najmniejszego sensu.

0

Nie chodzi o synchronizacje wątków z async/await. Tam jest sporo warunków z jaką serią ma się dokument z zamówieniem wystawić. Pobieram z bazy odpowiednie informacje asynchronicznie. C# nie pozwala na

lock(obj)
{
   var customers = await db.QueryAsync<Customer>("select * from CDN.Kontrahenci");
}

Skorzystam z synchronicznego db.Query i przetestuje czy coś to pomoże.

PS.
Ja sobie zdaje sprawę z tego jak działa API i wiem, że mam potrzebę na głupie rozwiązanie ale ja nie jestem w stanie kompletnie zasymulować takiego zachowania na moim urządzeniu. Robiłem to już na wszystkie sposoby. To dzieje się tylko u nich z powodów, które nie są mi znane. Nawet gość od biblioteki kazał mi timestamp ustawić na wywołaniu workera, bo w Androidzie jest coś takiego jak Doze Mode i żeby się nie włączał w tym samym czasie to trzeba go "zaifować".

0

Ja bym na szybko bazował na tym https://docs.microsoft.com/en[...]re-5.0&tabs=visual-studio czyli background service jako kolejka, o ile usługa jest w jednej instancji, synchronizacja operacji będzie prosta.

0

Background Worker w ASP .NET Core ma to do siebie, że przestaje działać w momencie jak IIS jest w trybie bezczynności. I uprzedzam od razu sugestię - nie, nie mogę tego hostować czymś innym, bo mi klient nie pozwala grzebać, a z dockerami nie mam doświadczenia.

1

Troszkę to głupie, ale nie możesz sam do siebie zrobić requestu http? Chociaż lepiej byłoby już zainstalować to jako serwis jak ma robić background joby, albo docker, bo to nie jest trudne w .netcore

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0