Wiele serwisów - zablokowanie jednoczesnego wykonywania danej operacji na tym samym obiekcie

Wiele serwisów - zablokowanie jednoczesnego wykonywania danej operacji na tym samym obiekcie
Potat0x
  • Rejestracja:ponad 8 lat
  • Ostatnio:16 dni
  • Postów:370
0

W mojej aplikacji (Spring) użytkownicy mogą dostać się do obiektów (nie chodzi o obiekty Javowe, a o aplikacje, którymi użytkownicy zarządzają) za pomocą UUID. Jest pewna operacja, która nie powinna zostać jednocześnie wiele razy uruchamiana dla danego UUID.

Mam takie proste rozwiązanie

Kopiuj
    @Service
    class Facade {
        private static final Set<String> locks = Collections.newSetFromMap(new ConcurrentHashMap<>());

        Either<Error, Result> foo(String uuid) {
            if (locks.contains(uuid)) {
                return "Error: operation in progress";
            }
            locks.add(uuid);
            try {
                return bar(uuid); //rozne operacje, miedzy innymi na bazie
            } finally {
                locks.remove(uuid);
            }
        }
    }

To będzie działać, jeżeli wystawię użytkownikom tylko jedną instancję mojej aplikacji (tytułowy serwis). Co, gdybym chciał to przeskalować? Co, gdyby skala była poważna?

  1. W moim przypadku operacja bar(uuid) jest wykonywana rzadko, a jej wielokrotne uruchomienie w tym samym czasie to efekt przypadku lub złośliwości użytkownika. Myślę, żeby po prostu dodać kolumnę w bazie, w której będzie przechowywany lock.
  2. I do sedna tego wątku. Co w odwrotnej sytuacji - częste próby wykonania jakiejś operacji (niewynikające z przypadków i złośliwości użytkowników), wielu użytkowników, wiele instancji serwisu na różnych maszynach? Słyszałem kiedyś o Redis - nie używałem. Czy to byłoby dobre rozwiązanie? Jak się rozwiązuje takie problemy?
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
2

Z wielu możluwych rozwiązań zwykle dobry wystarczająco jest optimistic lock.

Jest to bazodanowy odpowiednik compare and swap.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 2x, ostatnio: jarekr000000
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
4

Twój kod nie działa, masz race condition między sprawdzeniem seta a dodaniem elementu.
Nie wspomnę już o potencjalnym out of band exception i niewykonaniu finally (i niezwolnieniu blokady).

edytowany 1x, ostatnio: Afish
CS
  • Rejestracja:ponad 6 lat
  • Ostatnio:9 dni
  • Postów:296
0
Afish napisał(a):

Twój kod nie działa, masz race condition między sprawdzeniem seta a dodaniem elementu.
Nie wspomnę już o potencjalnym out of band exception i niewykonaniu finally (i niezwolnieniu blokady).

To pierwsze można chyba naprawić wywołując tylko add:

Kopiuj
@Service
class Facade {
    private static final Set<String> locks = Collections.newSetFromMap(new ConcurrentHashMap<>());

    Either<Error, Result> foo(String uuid) {
        if (!locks.add(uuid)) {
            return "Error: operation in progress";
        } else {
            try {
                return bar(uuid); //rozne operacje, miedzy innymi na bazie
            } finally {
                locks.remove(uuid);
            }
        }
    }
}
AF
Wygląda lepiej, ale ciągle ryzykujesz out of band exception.
Potat0x
  • Rejestracja:ponad 8 lat
  • Ostatnio:16 dni
  • Postów:370
0

@jarekr000000: ciekawy sposób, chociaż chodziło mi trochę o coś innego - o dosłowne zablokowanie wykonywania jakiejś czynności po stronie serwera (nie tylko transakcji w bazie).
@Afish u mnie działa :P A na poważnie to niezła kaszana. Ale skąd tam out of band exception?
@cs to jest dobre pytanie.

AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
1
Potat0x napisał(a):

Ale skąd tam out of band exception?

Coś ubije wątek, poleci OOM, poleci access violation. Pewnie jeszcze inne rzeczy by się znalazły.

Zobacz pozostałe 23 komentarze
jarekr000000
@Afish no to GRUBY design, patrząc na to, że komentarz w javadoc do thred.stop przez 20 lat się nie zmienił. Ale ponieważ ćwiczylem to już swego czasu to nadal uważam, że nie ma sensu pisać kodu odpornego na wyrafinowany sabotaż współprogramistów. Można i warto zabezpieczać się na poziomie kompilatora (choćby private), ale przed rozwalaniem JVM, grzebaniem po pamięci nie warto. W javie częściowo ratuje sytuację SecurityManger, który raz na 100 projektów ktoś ma odpalony, ale to raczej pomoże w zabezpieczniu się przed innymi libkami / aplikacjami na tym samym JVM.
AF
Kwestia gustu, nie mnie oceniać, czy to sabotaż, ja wolę skupić się na napisaniu poprawnego kodu, a nie na tropieniu deadlocków, więc po prostu zrobiłbym to porządnie. Pomijam już fakt, że tu nawet nie chodzi o ten konkretny przykład, tylko o zrozumienie problemu. Jutro OP będzie chciał rozproszyć locka, zaklepie to samemu na podobnej zasadzie i wpadnie w te same problemy, race condition przy dodawaniu do jakiejś bazy danych lub potem out of band exception i ubity proces. Zasada ta sama, tylko problem na poziomie wyżej, zamiast thread.stop ktoś wywoła process.kill.
Potat0x
@Afish zapakowałem dodawanie locków w try. Sytuacje typu sabotaż, ubijanie wątków, bugi w JVM wykluczam. Gorzej, że dalej (o ile dobrze myślę?) możliwe jest race condition w przykładzie, który podał @cs Jeżeli będę robił rozproszonego locka, to pewnie jeszcze się odezwę :)
AF
KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:23 minuty
  • Lokalizacja:Silesia/Marki
  • Postów:5505
4
Potat0x napisał(a):

Słyszałem kiedyś o Redis - nie używałem. Czy to byłoby dobre rozwiązanie?

Redis jest tylko cachem, taką rozbudowaną hashmapą. Bywa używany do synchronizacji/blokowania ponieważ zawiera TTLe (Time to Live). Jeśli blokujesz zasób za pomocą bazy danych i stanie się coś, że użytkownik jednak nie odblokuje zasobu to zasób już nigdy nie zostanie odblokowany. Bedziesz musiał wjechać do bazy danych na białym koniu i usunąć stare locki ręcznie. Ewentualnie możesz mieć skrypt puszczany w cronie, który będzie usuwać stare locki.

Ale jeśli używasz redisa to możesz tworzyć locki jako obiekty TTL. Czyli jeśli użytkowni nie odblokuje zasobu to, gdy minie czas TTL lock zostanie usunięty automatycznie.


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
4
Potat0x napisał(a):

@jarekr000000: ciekawy sposób, chociaż chodziło mi trochę o coś innego - o dosłowne zablokowanie wykonywania jakiejś czynności po stronie serwera (nie tylko transakcji w bazie).

Generalnie chodziło mi o nie blokowanie. W wielu wypadkach opłaca się tak naprawdę nie blokować, a normalnie przeprowadzać obliczenia itp... i dopiero na koniec, jeśli okaże się, że ktoś nas ubiegł i coś wcześniej przeliczył to po prostu porzucamy wyniki (np. przez rollback).

W ten sposób zwiększamy zużycie CPU i innych zasobów (na nonsensowne obliczenia), ale za to zwięszamy poziom zrównoleglenia (skalowalność). Im więcej nodów / rdzeni tym ten drugi sposób jest bardziej sensowny.

Synchronized może się zabawnie skończyć, co widziałem na jednej produkcji - 16 serwerów nic nie robi, kilka baz danych oracle nic nie robi, w IO nic się nie dzieje, a z aplikacji nie da się korzystać - wszystko czeka na synchronized (finalnie) na clusterowym locku :-)


jeden i pół terabajta powinno wystarczyć każdemu
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0
Kamil Żabiński napisał(a):

Ale jeśli używasz redisa to możesz tworzyć locki jako obiekty TTL. Czyli jeśli użytkowni nie odblokuje zasobu to, gdy minie czas TTL lock zostanie usunięty automatycznie.

Tu warto dodać, że po wzięciu takiego locka proces i tak musi sprawdzać, czy nie dostaliśmy pauzy przez GC lub wirtualizację i TTL w międzyczasie wygasł. Ogólnie to nie jest hop siup i łatwo zepsuć.

KamilAdam
Pracowałem tylko z długotrwającymi TTLami po 1h. Przy krótszych faktycznie to tak prosto nie zadziała
Potat0x
  • Rejestracja:ponad 8 lat
  • Ostatnio:16 dni
  • Postów:370
0

W odpowiedzi do tego komentarza

Kopiuj
    private static final Set<String> redeployLocks = Collections.newSetFromMap(new ConcurrentHashMap<>());
    

    Either<ErrorMessage, AppResponseDto> redeployApp(String appUuid, AppRequestDto requestDto) {
        try {
            if (redeployLocks.add(appUuid)) {
                return redeploy(appUuid, requestDto);
            } else {
                return Either.left(message("Redeploy already started", 429));
            }
        } finally {
            redeployLocks.remove(appUuid);
        }
    }

PS: właśnie zauważyłem nowy problem. Kto wie jaki? (zagadka :P)

edytowany 3x, ostatnio: Potat0x
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0

Teraz usuwasz locka wziętego przez kogoś innego.

edytowany 1x, ostatnio: Afish
Potat0x
  • Rejestracja:ponad 8 lat
  • Ostatnio:16 dni
  • Postów:370
0

Dokładnie. Teraz nie ma tego problemu:

Kopiuj
    Either<ErrorMessage, AppResponseDto> redeployApp(String appUuid, AppRequestDto requestDto) {
        if (redeployLocks.add(appUuid)) {
            try {
                return redeploy(appUuid, requestDto);
            } finally {
                redeployLocks.remove(appUuid);
            }
        } else {
            return Either.left(message("Redeploy already started", 429));
        }
    }

ale dalej może się zdarzyć równoległe wykonanie wnętrza if dla tego samego appUuid przez wiele wątków jednocześnie.

Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Postów:1873
0

Ale właśnie cały wątek jest o tym, że nie możesz tak zrobić. Musisz atomowo sprawdzić i założyć locka, aby żaden wątek się nie prześlizgnął. Poczytaj o klasie ReentrantLock i ogólnie czym jest sekcja krytyczna - na pewno zaprocentuje na przyszłość. (W niektórych scenariuszach możesz też jako implementację locka wykorzystać bazę danych).


”Engineering is easy. People are hard.” Bill Coughran
Potat0x
  • Rejestracja:ponad 8 lat
  • Ostatnio:16 dni
  • Postów:370
0

@Charles_Ray: właśnie z tego skorzystałem, teraz musi być ok.

Kopiuj
        boolean redeployNotRunning;
        redeployLocksSetLock.lock();
        try {
            redeployNotRunning = redeployLocks.add(appUuid);
        } finally {
            redeployLocksSetLock.unlock();
        }
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0

Teraz też nie masz pewności, że nie dostaniesz out of band między skończeniem lock a wejściem do try (chyba, że masz, to wtedy podaj źródła). W dotnecie na przykład JIT wstawiał nopa przed try i był deadlock.

Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Postów:1873
0

@Afish: możesz rozwinąć dlaczego nie ma takiej gwarancji w takim przypadku? Ten lock jest ogarniamy na poziomie systemu operacyjnego w tym przypadku i IMO to zadziała. To jest chyba najbardziej podstawowe użycie locków w Javie.


”Engineering is easy. People are hard.” Bill Coughran
edytowany 1x, ostatnio: Charles_Ray
Potat0x
  • Rejestracja:ponad 8 lat
  • Ostatnio:16 dni
  • Postów:370
0

@Afish: ale mówimy o jakichś sytuacjach typu błąd w JVM? Ciężko znaleźć w Google coś konkretnego na temat out of band (a na początku myślałem, że to literówka i chodzi o out of bounds :P).
Nawet w dokumentacji Javy jest

Kopiuj
   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }

więc o jeżeli nie ma błędu w Javie (zakładam, że nie ma), to po prostu musi działać poprawnie.

AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
2

Try-catch jest obsługiwany przez metadane mówiące skąd dokąd jest blok try. Jak skompilujesz kod javowy, to dostajesz bajtkod i tam jest podane, że od instrukcji X do Y jest try. To oznacza, że jeżeli nie zaczniesz wykonywać instrukcji X, to nie jesteś w bloku try, więc finally nie będzie wykonywany w przypadku wyjątku.

Teraz trzeba sobie uświadomić, że nasz kod javowy „nie ma znaczenia” — javac wykonuje optymalizacje, JIT optymalizuje jeszcze bardziej, potem jest poziom CPU z optymalizacjami read-write i podobnymi. To, co się wykonuje w rzeczywistości jest nierzadko czymś zupełnie innym od tego, co zaklepaliśmy. Proponuję poczytać o modelu pamięci i wielowątkowości.

JIT musi wziąć kod + metadane i w jakiś sposób obsłużyć wyjątek. Tam nie ma magii pod spodem, wyjątek jest zgłaszany na przykład przerwaniem sprzętowym i JVM sprawdza, co się stało i gdzie — może być użyty SEH, sygnały, SJLJ, ale w gruncie rzeczy sprowadza się to nieraz do porównywania rejestru IP lub tego typu podobnych. Teraz biorąc Twój kod:

Kopiuj
redeployLocksSetLock.lock();
try {

wszystko wygląda spoko dopóki metoda lock jest poprawna, czyli bierze locka i potem jesteśmy w bloku try. Ale jeżeli JIT skompiluje to do czegoś w stylu:

Kopiuj
1: call redeployLocksSetLock.lock()
2: nop
3: cokolwiek z try

czyli wstawi instrukcję nop między wywołaniem metody a blokiem try, to jeżeli jesteśmy w instrukcji 2 i dostajemy out-of-band exception, to formalnie nie jesteśmy jeszcze w try i finally się nie wykona.

Nie masz kontroli nad tym, co zrobi JIT, musisz mieć albo gwarancję od JVM lub kompilatora, że takich rzeczy nie robi, albo założyć, że może się to zdarzyć. Szukałem i pytałem na SO o takowe i nic nie dostałem, być może się mylę (jak ktoś ma źródło, to z chęcią przyjmę).

I teraz pytanie, czy teoretyzuję, czy to się dzieje? To był rzeczywisty bug w dotnecie związany z optymalizacjami: https://stackoverflow.com/a/34876564/1543037
I od razu dodam, że to nie jest coś „dotnet specific”, tylko tak działają komputery na pewnym poziomie, więc ten bug może kiedyś wrócić.

Sytuację, gdy JIT generuje nop dla łatwiejszego debugowania pominę, bo chyba nie wszystkie to robią, poza tym na produkcji raczej nie odpalamy wersji debug, więc jest to raczej bezpieczne (aczkolwiek też warto być tego świadomym).

edytowany 1x, ostatnio: Afish
Potat0x
Oczywiście, masz rację - ale zakładam, że nie ma bugów w Javie ;) Nawet, gdy był taki bug, to w mojej sytuacji jest baaardzo mało prawdopodobieństwo jego wystąpienia.
AF
Jak już uważasz, każdy zakłada, że nie ma bugów, a potem dla każdego jest to zaskoczenie, że te "nieprzewidziane" bugi się pojawiają.
damianem
  • Rejestracja:prawie 8 lat
  • Ostatnio:3 miesiące
  • Postów:205
1

Teraz trzeba sobie uświadomić, że nasz kod javowy „nie ma znaczenia”

No tak, zabieramy rzeczy i idziemy do domu, nic tu po nas... Oczywiście, że ma znaczenie - każda optymalizacja musi zachowywać semantykę programu który napisaliśmy. Gdyby tak nie było, Java i JVM byłyby chyba bezużyteczne, prawda?

Nie znam się na .NET, ale w przykładzie który przytoczyłeś dla mnie głównym problemem jest istnienie takiej funkcji jak Thread.Abort() - w Javie zrozumiano tak ze 20 lat temu, że przerywanie pracy wątku w dowolnym momencie to proszenie się o kłopoty i metoda Thread.stop() została oznaczona jako deprecated (w Javie 11 całkowicie usunięta).

AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0
damianem napisał(a):

Teraz trzeba sobie uświadomić, że nasz kod javowy „nie ma znaczenia”

No tak, zabieramy rzeczy i idziemy do domu, nic tu po nas... Oczywiście, że ma znaczenie - każda optymalizacja musi zachowywać semantykę programu który napisaliśmy. Gdyby tak nie było, Java i JVM byłyby chyba bezużyteczne, prawda?

Oczywiście, co nie znaczy, że kod wykonuje się tak, jak go napisaliśmy. Ponownie - model pamięci.

Nie znam się na .NET, ale w przykładzie który przytoczyłeś dla mnie głównym problemem jest istnienie takiej funkcji jak Thread.Abort() - w Javie zrozumiano tak ze 20 lat temu, że przerywanie pracy wątku w dowolnym momencie to proszenie się o kłopoty i metoda Thread.stop() została oznaczona jako deprecated (w Javie 11 całkowicie usunięta).

  1. Thread.Abort został wzięty z Javy (jak cały dotnet, który zerżnął nawet zwalone tablice), też był deprecated i też był usunięty. Nic to nie zmienia w kwestii out of band exceptions.
  2. Wątek ciągle można ubić z zewnątrz.
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
2
Afish napisał(a):
  1. Thread.Abort został wzięty z Javy (jak cały dotnet, który zerżnął nawet zwalone tablice), też był deprecated i też był usunięty. Nic to nie zmienia w kwestii out of band exceptions.
  2. Wątek ciągle można ubić z zewnątrz.

Można też pomazać po pamięci, mogą promienie kosmiczne locka zdjąć.
Sorry, ale już bardzo odjechałeś - model pamięci to jedno, ale podstawa JVM to właśnie fakt, że ma jakąś semantykę (bajtkod) i nie rozważamy w normalnym kodzie sytuacji abnormalnych, gdzie JVM funkcjonuje niepoprawnie. Nagłe ubicie wątku i pojawienie się wyjątku z d...y - to jest sytuacja nienormalna.

Pomijająć ten kod powyżej - ponieważ cały JVM jest pełny tak popisanych locków (podobnie, na ogół jest wprost CAS), to znaczy, że po ubiciu wątków z dużym prawdopodobieństwem powywala się IO, wewnętrzne struktury JVM, nie mówiąc już o bibliotekach typu Spring i podobne.

W praktyce oczywiście JVM ma bugi, a ludzie robią dziwne rzeczy (np. wywołania JNI) - zwykle wtedy JVM bardzo ładnie się składa core dumpem i problem się kończy.
Szansa, że ktoś będzie się tutaj nad deadlockiem męczył jest 0.

Tutak jako przykład podajesz ciągle, że ktoś może wywołać metodę, która jest oznaczona jako niebezpieczna. Poza twoim zespołem nie słyszałem o takiej akcji z Thread.stop() :-)
Typowe watchdogi i podobne w javie - raczje operują na interrupt, które jest bezpieczniejsze (choć wymaga jakiejś kooperacji od wątku, z tym jeśli to nasz wątek to nie jest to raczej proplem).

Z ciekawostek - kotlinowy .withLock - czyli to samo lambdą. ( I standard w kodzie kotlinowym)

Kopiuj
@kotlin.internal.InlineOnly
public inline fun <T> Lock.withLock(action: () -> T): T {
    lock()
    try {
        return action()
    } finally {
        unlock()
    }
}

jeden i pół terabajta powinno wystarczyć każdemu
edytowany 4x, ostatnio: jarekr000000
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0
jarekr000000 napisał(a):

nie rozważamy w normalnym kodzie sytuacji abnormalnych, gdzie JVM funkcjonuje niepoprawnie.

Ja też tego tutaj nie rozważam. Wszystkie opisane przez mnie sytuacje są w pełni dozwolone (jeżeli się mylę, to wskaż, może JVM rzeczywiście ma jakieś gwarancje, których nie mają inne platformy).

jarekr000000 napisał(a):

Nagłe ubicie wątku i pojawienie się wyjątku z d...y - to jest sytuacja nienormalna.

Nie mnie oceniać, czy normalna, czy nie. Takie rzeczy się dzieją i tyle.

jarekr000000 napisał(a):

Tutak jako przykład podajesz ciągle, że ktoś może wywołać metodę, która jest oznaczona jako niebezpieczna. Poza twoim zespołem nie słyszałem o takiej akcji z Thread.stop() :-)

Trudno, nic nie poradzę.

jarekr000000 napisał(a):

Z ciekawostek - kotlinowy .withLock - czyli to samo lambdą. ( I standard w kodzie kotlinowym)

Kopiuj
@kotlin.internal.InlineOnly
public inline fun <T> Lock.withLock(action: () -> T): T {
    lock()
    try {
        return action()
    } finally {
        unlock()
    }
}

Dotnet też to tak implementował, a potem zrozumieli, że to się może wysypać i zmienili implementację.

Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Postów:1873
0

Offtopic się zrobił. Podążając tym tokiem myślenia należałoby w ogóle zrezygnować z JVM na rzecz bardziej niskopoziomowego rozwiązania :) reasumując, jeśli JVM, to podane rozwiązanie jest optymalne w tym zakresie.


”Engineering is easy. People are hard.” Bill Coughran
edytowany 1x, ostatnio: Charles_Ray
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
2
Afish napisał(a):
jarekr000000 napisał(a):

nie rozważamy w normalnym kodzie sytuacji abnormalnych, gdzie JVM funkcjonuje niepoprawnie.

Ja też tego tutaj nie rozważam. Wszystkie opisane przez mnie sytuacje są w pełni dozwolone (jeżeli się mylę, to wskaż, może JVM rzeczywiście ma jakieś gwarancje, których nie mają inne platformy).

Wskazuję, że nie są dozwolone:
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#stop--

Poza tym jeśli dopuszczamy możliwość, że część kodu jest niegodna zaufania to wtedy można włączyć SecurityManagera, który wprost uniemożliwi wywołanie Thread.stop i podobnych.
Robi sie to wyjątkowo, piszac serwery aplikacji czy kontener appletów itp..

Nadal nie chroni to, przed wyjściem z wątku przez OOM, ale tu z kolei mamy złożenie się JVM po takim przypadku + z tego co kojarze akurat to finally wtedy zadziała

Jeszcze mam propozycję, żebyś podał kod bezpieczniejszy.
.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 2x, ostatnio: jarekr000000
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0
jarekr000000 napisał(a):

Wskazuję, że nie są dozwolone:
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#stop--

Ja mówię o optymalizacjach na poziomie JIT-a, a Ty mi pokazujesz komentarze w javadocu…

Nadal nie chroni to, przed wyjściem z wątku przez OOM, ale tu z kolei mamy złożenie się JVM po takim przypadku + z tego co kojarze akurat to finally wtedy zadziała

Finally wykona się w przypadku OOM użytkownika, tak samo jak wykona się w przypadku ThreadDeath. Możesz też złapać OOM w catchu i normalnie kontynuować. I zanim odbijesz mnie, że to herezja, to od razu powiem, że takie rzeczy widziałem.

Nie wiem, czy finally wykona się w przypadku OOM rzuconego przez alokację z JVM-a, ale takich sytuacji tu nie rozważamy póki co.

Jeszcze mam propozycję, żebyś podał kod bezpieczniejszy.

Weź blokadę w bloku try i odpowiednio zwolnij w finally.

@jarekr000000: Pokazałem potencjalny błąd z wyjaśnieniem jego istoty, a Ty cały czas odbijasz mnie, że nigdy nie widziałeś thread.stop w akcji. Dla mnie ta rozmowa nigdzie nie zmierza, więc jak nie masz innych argumentów, to ja już podziękuję.

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1
Afish napisał(a):

Weź blokadę w bloku try i odpowiednio zwolnij w finally.

Czyli jak rozumiem postulujesz taki kod:

Kopiuj
public void m() {
     try {
       lock.lock();  // block until condition holds
       // ... method body
     } finally {
       lock.unlock()
     }
   }

Jesteś świadom jakie są konsekwencje tego kwiatka? W kontekście scenariusza podanego przez OP.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0

Czym jest lock w Twoim kodzie? Jeżeli to coś nie śledzi właściciela, to w finally odblokujesz locka, którego wziął ktoś inny (czyli nie jest to "odpowiednio zwolnij w finally"). Jeżeli to coś śledzi, ale przy próbie odblokowania z innego wątku rzuca wyjątek, to też niedobrze (i znowu nie jest to "odpowiednio zwolnij w finally"). A jak śledzi i nie rzuca, to ciągle pozostaje problem rekurencji. W każdym razie nie wiem, czego próbujesz dowieść przez pokazanie błędnej implementacji.

Błąd może wystąpić (a przynajmniej nie udowodniłeś, że jest inaczej), wprawdzie szanse są minimalne, ale niezerowe i takie rzeczy się dzieją. To już Twój wybór, co z tym zrobisz.

edytowany 1x, ostatnio: Afish
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
2

@Afish

Cały czas próbuję dostać od Ciebie propozycję dobrej implementacji. Pokaż kod.
Jak działa lock możesz zobaczyć tutaj:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html
Możesz sobie wybrać jedną z 2 dostępnych implementacji, albo napisać własną.

EDIT:
To nie jest tak, że się nie da, problem leży w czym innym, ale chętnie popatrzę jak się męczysz.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 3x, ostatnio: jarekr000000
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:15 dni
0

Dzięki, nie skorzystam. Pisałem już locki do specyficznych potrzeb i wiem, że to nie jest łatwe, a tego problemu w Javie jeszcze nie musiałem rozwiązywać (w ważnych miejscach obchodziłem go na wyższym poziomie), więc nie mam kodu, który pasowałby do podanego przez Ciebie interfejsu i jednocześnie robił robotę.

Jak taka implementacja miałaby wyglądać? Gdyby musiał to pisać w Javie, to pewnie szedłbym w stronę CAS na współdzielonej pamięci, gdzie zapisywałbym ciasteczko od wątku. Inne wątki musiałyby jeszcze wykryć, że wątek trzymający blokadę umarł i zwalniać po jakimś czasie. Kiedyś używałem czegoś podobnego (między procesami), jak jesteś zainteresowany, to popatrz tutaj: https://blog.adamfurmanek.pl/?p=3148&preview=1&_ppp=11d8547ad4 Gdybym musiał to pisać inaczej, to pewnie zrobiłbym cywilizowany mechanizm ustawiający atomowo flagę (zerżnąłbym go pewnie z dotneta). I zapewne przy implementacji nadziałbym się na masę problemów (chociażby różne platformy lub utrzymywanie sprawiedliwej kolejki), nie przeczę.

Mam wrażenie, że skupiłeś się na atakowaniu mnie, zamiast na analizie problemu. Ja nie mówię, żebyś przepisał cały swój kod, ani nie zależy mi na tym, żebyś mi uwierzył, że problem występuje, ba, nawet zgadzam się, że zazwyczaj nie ma co z tym walczyć, tylko lepiej ubić JVM. Jak nie masz thread.stop i pozwalasz maszynie umrzeć, to pewnie nawet w przypadku wystąpienia problemu nie odczujesz konsekwencji. Ale jednocześnie pracowałem w projekcie, gdzie śmierć procesu była bardzo niepożądana i różne sytuacje były brane pod rozwagę, a Twoje podejście „Mogę zrobić taki test w ile minut po wrzucieniu Thread.stop do źródeł dostanę wypowiedzenie...” nie byłoby żadnym usprawiedliwieniem. Tylko, że ten temat dotyczy locka między różnymi maszynami, a tam problemy w stylu „process.kill” występują nagminnie i warto o tym pamiętać, więc zwróciłem autorowi uwagę, że jego kod może nie być idealny (w rozumieniu, że gdyby taki kod przeniósł na poziom wyżej, to miałby o wiele większą szansę awarii).

A odnośnie „chętnie popatrzę jak się męczysz”, to mam nadzieję, że nie prezentujesz takiego podejścia w pracy.

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

Ja w ogóle zrobiłbym to raczje bez blockad - tylko coś nieblokujacego w stylu CAS - bo zwykle się da.
Ale jak juz ktoś chce się bawić w locki to podane rozwiązanie (try po lock) jest dość optymalne, ponieważ potencjalne ryzyko problemu jest znikowe ( złośliwy kod, złe działanie JVM), a straty małe (zablokowany jeden zasób),
Alternatywy to np:

  • lock w try to równie małe prawdpowodobienstwo problemu, przy potencjalnie gorszych szkodach (wielokrotne użycie zasobu)
  • dużo bardziej skomplikowany kod (tak jak piszesz wyżej) - IMO nie warto.

Rozumiem, że masz jakiś uraz do tego Thread.stop, może w .NET problem zdychania wątków bardziej występuje, (przez unsafe? ) ale nie warto się zabezpieczać na wszystko, trzeba mierzyć środki do wybranego problemu i szkód.
Inaczej wpadamy w paranoję i efekt ostateczny to poryty, pełny bugów, kod blokad, którego nikt nie ogarnia, a szczególnie autor. Przy okazji - dawno, dawno temu widziałem to w akcji - więc akurat ja mam osobisty uraz do tego podejścia.
Jak będę pisał kod krytyczny w elektrowni atomowej: - a) to takie problemy rozważę, b) nie zrobię tego w javie :-)

(ogólnie to kwestie sekcji krytycznej z pewnymi gwarancjami rozwiązuje w javie słówko synchronized, ale akurat tu nie widzę prostego użycia no i synchronized też ma swoje problemy).


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 2x, ostatnio: jarekr000000
AF
E tam, nie mam urazu, po prostu miałem potrzebę zaklepania i zwracam uwagę na takie rzeczy. Ktoś te locki pisze, jak mam potrzebę, to też tak robię, co nie znaczy, że nie ufam platformie i od razu rzucam się na takie rzeczy.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)