Jak debugujecie błąd, który występuje tylko na produkcji?

0

Xamarin, ASP NET ZERO
Cześć wszystkim. Testuję aplikację mobilną na produkcji. Wszystko działa idealnie poza jednym: Po kliknięciu w usunięcie elementu z listy aplikacja "wysypuje się" ale nie zawsze i tu cały pies pogrzebany. Sprawdziłem nawet id obiektu w bazie danych i wszystko się zgadza. Ponad to po wysypaniu apki i ponownym włączeniu element jest poprawnie usunięty, więc _vehicleTasksAppService działa poprawnie. Co najgorsze, ten problem występuje tylko na produkcji, w środowisku testowym nie wysypuje się nic. Tutaj kod obsługi komendy usunięcia elementu:

public ICommand RemoveTaskTappedCommand => new Command(async (vehicleTask) => await RemoveTaskTappedAsync((VehicleTaskModel)vehicleTask));
        private async Task RemoveTaskTappedAsync(VehicleTaskModel vehicleTask)
        {
            Debug.WriteLine("RemoveTaskTappedAsync " + vehicleTask.Title + "(" + vehicleTask.Id + ")");
            await SetBusyAsync(async () =>
            {
                await _vehicleTasksAppService.Delete(new Abp.Application.Services.Dto.EntityDto<Guid>(vehicleTask.Id));
            });
            await ResetAndRefreshAsync();
            MessagingCenter.Send(this, MessagingCenterKeys.VehicleTasksChanged);
        }
        private async Task ResetAndRefreshAsync()
        {
            IsRefreshing = false;
            if (IsNotBusy)
            {
                VehicleTasks.GetAllInput = new GSData.VehicleTasks.Dtos.GetAllVehicleTasksInput()
                {
                    Filter = "",
                    MaxResultCount = PageDefaults.PageSize,
                    SkipCount = 0,
                    VehicleId = _vehicleId,
                    Sorting = "CreatingTime desc",
                    OnlyIncompleted = _hideCompletedTasks
                };
                VehicleTasks.Reset();

                await RefreshAsync();
            }
        }
        private async Task RefreshAsync()
        {
            await SetBusyAsync(async () =>
            {
                // vehicle
                var vehicleDto = await _vehiclesAppService.GetVehicleForView(_vehicleId);
                var vehicle = new VehicleModel()
                {
                    Producer = vehicleDto.Vehicle.Producer,
                    Model = vehicleDto.Vehicle.Model,
                    RegistrationNumber = vehicleDto.Vehicle.RegistrationNumber,
                    Id = vehicleDto.Vehicle.Id,
                    ToDoAll = vehicleDto.TotalTaskCount,
                    ToDoIncompleted = vehicleDto.TotalTaskCount - vehicleDto.TotalCompletedTaskCount,
                };
                Vehicle = vehicle;

                // VehicleTaskList
                await WebRequestExecuter.Execute(async () => await _vehicleTasksAppService.GetAll(VehicleTasks.GetAllInput), result =>
                {
                    foreach (var vehicleTask in result.Items)
                    {
                        VehicleTasks.Collection.Add(new VehicleTaskModel()
                        {
                            Id = vehicleTask.VehicleTask.Id,
                            CreatingTime = vehicleTask.VehicleTask.CreatingTime,
                            CompletedTime = vehicleTask.VehicleTask.CompletedTime,
                            Description = vehicleTask.VehicleTask.Description,
                            Title = vehicleTask.VehicleTask.Title,
                        });
                    }
                    VehicleTasks._totalProducts = result.TotalCount;
                    return Task.CompletedTask;
                });

            });
        }
public class GSObservableCollection<CollectionType, GetAllInputType>
    {
        public GSObservableCollection()
        {
            Collection = new ObservableRangeCollection<CollectionType>();
 
            _totalProducts = 0;
            _currentPage = 0;
        }
        public int _totalProducts { get; set; }
        public int _currentPage { get; set; }

        public ObservableRangeCollection<CollectionType> Collection { get; set; }

        public GetAllInputType GetAllInput { get; set; }

        public void Reset()
        {
            Collection.Clear();
            _totalProducts = 0;
            _currentPage = 0;
        }
    }

Dodam, że SetBusyAsync jest opakowane w przechwytywanie wyjątków, więc nie powinno się nic dziać specjalnego.
Jak sobie radzicie z debugowaniem aplikacji w środowisku produkcyjnym?
Macie jakieś pomysły lub widzicie co mogę robić źle? ;/

3

Czy masz jeszcze jakieś informację? StackTrace, coś z loggera, bo ciężko z tego fragmentu coś wywnioskować, może nawet nie tutaj jest problem.
Oczywiście lepiej nie testować na produkcji, ale kto tego nie robił :D, no i cóż, masz jakiś edge case, ja bym zreplikował środowisko i zaglądał w logi aplikacji, bazy etc. żeby chociaż mieć pojęcie gdzie to leci

6

Do naprawiania produkcyjnych błędów w ogólnym przypadku możesz użyć chyba tylko logów. Dlatego bardzo ważne jest logowanie wszystkich wyjątków wraz ze stack trace oraz danych do ewentualnego lokalnego debugowania.
W szczególnych - możesz spróbować odtworzyć błąd lokalnie u siebie (ale nie w Twoim przypadku) albo wpiąć się debugerem do produkcji, ale to już jest hardcore i chyba też nie do zrealizowania u Ciebie.

2

Zazwyczaj staram się po prostu odtworzyć błąd lokalnie, zawsze to po jakimś czasie wychodzi. Do odtworzenia błędu skompiluj i uruchamiaj sobie w trybie release bez debugera bo niektóre zachowania się mogą zmienić.

Łączenie wątków, async/await, zmienianie boolowych wartości i poleganie na nich to słabe połączenie. Kompilator może sobie zmienić kolejność instrukcji jeśli nie ma Thread.MemoryBarrier, locków, jeśli pola nie są volatile to jeden wątek może sobie coś przeczytać wcześniej i to zapamiętać, albo coś może zdążyć się wykonać przed czymś innym i dwa wątki lądują nie tam gdzie trzeba.
Zrób porządną synchronizację i zabezpieczenia przez semafory, locki itp. Blokowanie i sprawdzenie blokady powinno być operacją atomową, nie możesz pisać kodu typu if (!busy) { busy = true; Do(); } w wielowątkowej aplikacji. Podejrzewam że user po prostu wystarczająco szybko naciska kilka razy na usunięcie a SetBusyAsync jest wadliwe.

Skoro wysypuje się cała aplikacja a masz wszystko opakowane w try to znaczy że gdzieś masz taska zwracającego void albo błąd leci na jakimś niższym poziomie; nie próbujesz robić zapytania HTTP w głównym wątku? Android na to nie pozwala i może wysypać aplikację. Zrobienie zapytania w metodzie asynchronicznej nie gwarantuje że nie wykona się na głównym wątku.

1

Widząc tylko taki mały kontekst, strzelam że na 90% to jest problem z wątkami. Poza tym pomyśl o przesiadce na MAUI :)

1

Skoro temat ożywion, to podeślę jedynie info, że https://rr-project.org/ bywa użyteczne w tego typu sytuacjach :-)

(due diligence: nie jestem pewien jak/czy sprawdza się z C# + Xamarin, though.)

1

Problem został rozwiązany, przepraszam, że nie napisałem. Metoda SetBusyAsync przechwytywała tylko konkretny typ wyjątku. Dziwne tylko ze nie wyłapało mi tego. Dziękuję wszystkim za zaangażowanie i pomysły!

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